@zleap-ai/sag-cli 0.5.0 → 0.6.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/README.en.md +41 -9
- package/README.md +31 -9
- package/dist/cli.js +2906 -330
- package/dist/cli.js.map +1 -1
- package/dist/sag-knowledge.zip +0 -0
- package/dist/skill-cli.cjs +172 -0
- package/package.json +2 -2
- package/skill/SKILL.md +48 -46
- package/skill/manifest.json +8 -0
- package/skill/references/citation-rules.md +5 -83
- package/skill/references/cli-reference.md +31 -205
- package/skill/references/ndjson-contract.md +38 -0
- package/skill/references/safety.md +17 -0
- package/skill/references/search-strategies.md +5 -71
- package/skill/scripts/sag-knowledge.cjs +172 -0
- package/skills/sag-mcp/SKILL.md +4 -0
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import { mkdtemp, rm as rm3 } from "fs/promises";
|
|
5
|
+
import os5, { tmpdir } from "os";
|
|
6
|
+
import path15 from "path";
|
|
5
7
|
import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
6
8
|
|
|
7
9
|
// package.json
|
|
8
10
|
var package_default = {
|
|
9
11
|
name: "@zleap-ai/sag-cli",
|
|
10
|
-
version: "0.
|
|
12
|
+
version: "0.6.0",
|
|
11
13
|
description: "Command-line client and diagnostics for SAG knowledge bases",
|
|
12
14
|
type: "module",
|
|
13
15
|
bin: {
|
|
@@ -22,7 +24,7 @@ var package_default = {
|
|
|
22
24
|
"skills/sag-mcp"
|
|
23
25
|
],
|
|
24
26
|
scripts: {
|
|
25
|
-
build: "tsup",
|
|
27
|
+
build: "tsup && node scripts/sync-skill-runtime.mjs",
|
|
26
28
|
dev: "tsx src/cli.ts",
|
|
27
29
|
test: "vitest run",
|
|
28
30
|
"test:watch": "vitest",
|
|
@@ -94,6 +96,32 @@ import path from "path";
|
|
|
94
96
|
import { z } from "zod";
|
|
95
97
|
|
|
96
98
|
// src/core/errors.ts
|
|
99
|
+
function toSkillErrorCode(code) {
|
|
100
|
+
switch (code) {
|
|
101
|
+
case "AUTH_REQUIRED":
|
|
102
|
+
case "PERMISSION_DENIED":
|
|
103
|
+
case "NETWORK_UNREACHABLE":
|
|
104
|
+
case "INVALID_ARGUMENT":
|
|
105
|
+
case "MCP_FAILED":
|
|
106
|
+
case "HOST_NOT_FOUND":
|
|
107
|
+
case "HOST_CONFIG_FAILED":
|
|
108
|
+
case "CONFIG_CONFLICT":
|
|
109
|
+
case "DEPENDENCY_MISSING":
|
|
110
|
+
case "INVALID_RESPONSE":
|
|
111
|
+
case "INTERNAL_ERROR":
|
|
112
|
+
return code;
|
|
113
|
+
case "EXTERNAL_DEPENDENCY_MISSING":
|
|
114
|
+
return "DEPENDENCY_MISSING";
|
|
115
|
+
case "SERVICE_NOT_READY":
|
|
116
|
+
return "PLATFORM_UNAVAILABLE";
|
|
117
|
+
case "RESOURCE_NOT_FOUND":
|
|
118
|
+
return "DOCUMENT_NOT_FOUND";
|
|
119
|
+
case "DOCUMENT_NOT_READY":
|
|
120
|
+
return "PROCESSING_FAILED";
|
|
121
|
+
default:
|
|
122
|
+
return "INTERNAL_ERROR";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
97
125
|
var CliError = class extends Error {
|
|
98
126
|
code;
|
|
99
127
|
exitCode;
|
|
@@ -145,6 +173,14 @@ function toCliError(error) {
|
|
|
145
173
|
|
|
146
174
|
// src/agents/adapter.ts
|
|
147
175
|
import { createHash } from "crypto";
|
|
176
|
+
async function isAgentInstalled(adapter) {
|
|
177
|
+
try {
|
|
178
|
+
await adapter.detect();
|
|
179
|
+
return true;
|
|
180
|
+
} catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
148
184
|
function canonicalize(value) {
|
|
149
185
|
if (Array.isArray(value)) {
|
|
150
186
|
return value.map(canonicalize);
|
|
@@ -476,8 +512,8 @@ function parseTomlTablePath(line) {
|
|
|
476
512
|
function findHeaderTables(config, name) {
|
|
477
513
|
const target = ["mcp_servers", name, "http_headers"];
|
|
478
514
|
return config.split(/\r?\n/u).flatMap((line, index) => {
|
|
479
|
-
const
|
|
480
|
-
return
|
|
515
|
+
const path16 = parseTomlTablePath(line);
|
|
516
|
+
return path16 && path16.length === target.length && path16.every((part, partIndex) => part === target[partIndex]) ? [index] : [];
|
|
481
517
|
});
|
|
482
518
|
}
|
|
483
519
|
function readHttpHeaders(config, name) {
|
|
@@ -1443,6 +1479,8 @@ function defaultManagedConnectionsPath(options = {}) {
|
|
|
1443
1479
|
var defaultEnterpriseMcpConnectionsPath = (options = {}) => path6.join(path6.dirname(defaultConfigPath(options)), "mcp-connections.yaml");
|
|
1444
1480
|
var defaultEnterpriseMcpManagedStatePath = (options = {}) => path6.join(path6.dirname(defaultConfigPath(options)), "mcp-managed-state.yaml");
|
|
1445
1481
|
var defaultEnterpriseMcpCredentialsPath = (options = {}) => path6.join(path6.dirname(defaultConfigPath(options)), "mcp-credentials.json");
|
|
1482
|
+
var defaultSkillStatePath = (options = {}) => path6.join(options.homeDirectory ?? os4.homedir(), ".sag", "skill", "state.json");
|
|
1483
|
+
var defaultSkillAccountConfigPath = (options = {}) => path6.join(options.homeDirectory ?? os4.homedir(), ".sag", "config.json");
|
|
1446
1484
|
|
|
1447
1485
|
// src/credentials/file.ts
|
|
1448
1486
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -1594,11 +1632,13 @@ var MemoryCredentialStore = class {
|
|
|
1594
1632
|
}
|
|
1595
1633
|
};
|
|
1596
1634
|
|
|
1597
|
-
// src/credentials/
|
|
1598
|
-
var
|
|
1635
|
+
// src/credentials/namespaces.ts
|
|
1636
|
+
var PERSONAL_SERVICE_NAME = "@zleap-ai/sag-cli";
|
|
1599
1637
|
var ENTERPRISE_MCP_SERVICE_NAME = "@zleap-ai/sag-cli/enterprise-mcp";
|
|
1638
|
+
|
|
1639
|
+
// src/credentials/keychain.ts
|
|
1600
1640
|
var KeychainCredentialStore = class {
|
|
1601
|
-
constructor(entryFactory, serviceName =
|
|
1641
|
+
constructor(entryFactory, serviceName = PERSONAL_SERVICE_NAME) {
|
|
1602
1642
|
this.entryFactory = entryFactory;
|
|
1603
1643
|
this.serviceName = serviceName;
|
|
1604
1644
|
}
|
|
@@ -2681,46 +2721,562 @@ var NodeProcessRunner = class {
|
|
|
2681
2721
|
}
|
|
2682
2722
|
};
|
|
2683
2723
|
|
|
2724
|
+
// src/skill/state-store.ts
|
|
2725
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2726
|
+
import { mkdir as mkdir7, open as open3, readFile as readFile9, rename as rename8, unlink as unlink8 } from "fs/promises";
|
|
2727
|
+
import path10 from "path";
|
|
2728
|
+
import { lock as lock6 } from "proper-lockfile";
|
|
2729
|
+
import { z as z10 } from "zod";
|
|
2730
|
+
var agentSchema = z10.enum(["codex", "claude-code", "workbuddy"]);
|
|
2731
|
+
var digestSchema = z10.string().regex(/^sha256:[a-f0-9]{64}$/u);
|
|
2732
|
+
var managedSkillSchema = z10.strictObject({
|
|
2733
|
+
agent: agentSchema,
|
|
2734
|
+
backupPath: z10.string().min(1).optional(),
|
|
2735
|
+
fingerprint: digestSchema,
|
|
2736
|
+
target: z10.string().min(1),
|
|
2737
|
+
version: z10.string().min(1)
|
|
2738
|
+
});
|
|
2739
|
+
var skillStateSchema = z10.strictObject({
|
|
2740
|
+
schema: z10.literal(2),
|
|
2741
|
+
managedSkills: z10.record(z10.string(), managedSkillSchema)
|
|
2742
|
+
});
|
|
2743
|
+
function emptyState5() {
|
|
2744
|
+
return { schema: 2, managedSkills: {} };
|
|
2745
|
+
}
|
|
2746
|
+
function invalidState5(filePath) {
|
|
2747
|
+
return new CliError("CONFIG_CONFLICT", "Skill installation state is invalid", {
|
|
2748
|
+
exitCode: exitCodes.configConflict,
|
|
2749
|
+
hint: `Remove the unpublished legacy state at ${filePath}, then reinstall the Skill.`
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
function managedSkillKey(agent, target) {
|
|
2753
|
+
return `${agent}:${target}`;
|
|
2754
|
+
}
|
|
2755
|
+
var SkillStateStore = class {
|
|
2756
|
+
constructor(filePath) {
|
|
2757
|
+
this.filePath = filePath;
|
|
2758
|
+
}
|
|
2759
|
+
filePath;
|
|
2760
|
+
async #withLock(operation) {
|
|
2761
|
+
await mkdir7(path10.dirname(this.filePath), { recursive: true });
|
|
2762
|
+
let release;
|
|
2763
|
+
try {
|
|
2764
|
+
release = await lock6(this.filePath, {
|
|
2765
|
+
realpath: false,
|
|
2766
|
+
stale: 1e4,
|
|
2767
|
+
update: 2e3,
|
|
2768
|
+
retries: { retries: 100, factor: 1, minTimeout: 10, maxTimeout: 10 }
|
|
2769
|
+
});
|
|
2770
|
+
} catch {
|
|
2771
|
+
throw new CliError("CONFIG_CONFLICT", "Skill installation state is busy", {
|
|
2772
|
+
exitCode: exitCodes.configConflict
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
try {
|
|
2776
|
+
return await operation();
|
|
2777
|
+
} finally {
|
|
2778
|
+
await release();
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
async load() {
|
|
2782
|
+
try {
|
|
2783
|
+
return skillStateSchema.parse(JSON.parse(await readFile9(this.filePath, "utf8")));
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
if (error.code === "ENOENT") return emptyState5();
|
|
2786
|
+
throw invalidState5(this.filePath);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
async #saveUnlocked(state) {
|
|
2790
|
+
const validated = skillStateSchema.parse(state);
|
|
2791
|
+
const directory = path10.dirname(this.filePath);
|
|
2792
|
+
await mkdir7(directory, { recursive: true });
|
|
2793
|
+
const temporaryPath = path10.join(
|
|
2794
|
+
directory,
|
|
2795
|
+
`.${path10.basename(this.filePath)}.${randomUUID4()}.tmp`
|
|
2796
|
+
);
|
|
2797
|
+
let temporaryCreated = false;
|
|
2798
|
+
try {
|
|
2799
|
+
const handle = await open3(temporaryPath, "wx", 384);
|
|
2800
|
+
temporaryCreated = true;
|
|
2801
|
+
try {
|
|
2802
|
+
await handle.chmod(384);
|
|
2803
|
+
await handle.writeFile(JSON.stringify(validated), "utf8");
|
|
2804
|
+
} finally {
|
|
2805
|
+
await handle.close();
|
|
2806
|
+
}
|
|
2807
|
+
await rename8(temporaryPath, this.filePath);
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
if (temporaryCreated) await unlink8(temporaryPath).catch(() => void 0);
|
|
2810
|
+
throw error;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
async getManagedSkill(agent, target) {
|
|
2814
|
+
return (await this.load()).managedSkills[managedSkillKey(agent, target)] ?? null;
|
|
2815
|
+
}
|
|
2816
|
+
async saveManagedSkill(managedSkill) {
|
|
2817
|
+
const validated = managedSkillSchema.parse(managedSkill);
|
|
2818
|
+
await this.#withLock(async () => {
|
|
2819
|
+
const state = await this.load();
|
|
2820
|
+
state.managedSkills[managedSkillKey(validated.agent, validated.target)] = validated;
|
|
2821
|
+
await this.#saveUnlocked(state);
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
async deleteManagedSkill(agent, target) {
|
|
2825
|
+
await this.#withLock(async () => {
|
|
2826
|
+
const state = await this.load();
|
|
2827
|
+
delete state.managedSkills[managedSkillKey(agent, target)];
|
|
2828
|
+
await this.#saveUnlocked(state);
|
|
2829
|
+
});
|
|
2830
|
+
}
|
|
2831
|
+
};
|
|
2832
|
+
|
|
2833
|
+
// src/skill/account-auth-client.ts
|
|
2834
|
+
import { z as z12 } from "zod";
|
|
2835
|
+
|
|
2836
|
+
// src/skill/account-config.ts
|
|
2837
|
+
import { createHash as createHash2, randomUUID as randomUUID5 } from "crypto";
|
|
2838
|
+
import { mkdir as mkdir8, open as open4, readFile as readFile10, rename as rename9, unlink as unlink9 } from "fs/promises";
|
|
2839
|
+
import path11 from "path";
|
|
2840
|
+
import { lock as lock7 } from "proper-lockfile";
|
|
2841
|
+
import { z as z11 } from "zod";
|
|
2842
|
+
var recentKnowledgeBaseSchema = z11.strictObject({
|
|
2843
|
+
id: z11.string().min(1),
|
|
2844
|
+
name: z11.string().min(1)
|
|
2845
|
+
});
|
|
2846
|
+
var skillAccountSchema = z11.strictObject({
|
|
2847
|
+
accessToken: z11.string().min(1),
|
|
2848
|
+
refreshToken: z11.string().min(1),
|
|
2849
|
+
accessExpiresAt: z11.string().datetime({ offset: true }),
|
|
2850
|
+
refreshExpiresAt: z11.string().datetime({ offset: true }),
|
|
2851
|
+
user: z11.strictObject({ id: z11.string().min(1), name: z11.string().min(1) }),
|
|
2852
|
+
recentKnowledgeBase: recentKnowledgeBaseSchema.optional()
|
|
2853
|
+
});
|
|
2854
|
+
var skillAccountConfigSchema = z11.object({
|
|
2855
|
+
version: z11.literal(1),
|
|
2856
|
+
currentOrigin: z11.string().optional(),
|
|
2857
|
+
accounts: z11.record(z11.string(), skillAccountSchema)
|
|
2858
|
+
}).passthrough();
|
|
2859
|
+
function configurationConflict(message, filePath) {
|
|
2860
|
+
return new CliError("CONFIG_CONFLICT", message, {
|
|
2861
|
+
exitCode: exitCodes.configConflict,
|
|
2862
|
+
hint: filePath ? `Review or restore ${filePath}.` : "Provide a pure SAG Origin."
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
function normalizeSkillOrigin(input2) {
|
|
2866
|
+
try {
|
|
2867
|
+
const parsed = new URL(input2);
|
|
2868
|
+
if (!["http:", "https:"].includes(parsed.protocol) || !parsed.hostname || parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
2869
|
+
throw new Error("not a pure HTTP Origin");
|
|
2870
|
+
}
|
|
2871
|
+
return parsed.origin;
|
|
2872
|
+
} catch {
|
|
2873
|
+
throw configurationConflict("SAG address must be a pure HTTP or HTTPS Origin");
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
var SkillAccountConfigStore = class {
|
|
2877
|
+
constructor(filePath) {
|
|
2878
|
+
this.filePath = filePath;
|
|
2879
|
+
}
|
|
2880
|
+
filePath;
|
|
2881
|
+
async load() {
|
|
2882
|
+
let raw;
|
|
2883
|
+
try {
|
|
2884
|
+
raw = await readFile10(this.filePath, "utf8");
|
|
2885
|
+
} catch (error) {
|
|
2886
|
+
if (error.code === "ENOENT") {
|
|
2887
|
+
return { version: 1, accounts: {} };
|
|
2888
|
+
}
|
|
2889
|
+
throw error;
|
|
2890
|
+
}
|
|
2891
|
+
try {
|
|
2892
|
+
return skillAccountConfigSchema.parse(JSON.parse(raw));
|
|
2893
|
+
} catch {
|
|
2894
|
+
throw configurationConflict(
|
|
2895
|
+
"SAG account configuration is invalid",
|
|
2896
|
+
this.filePath
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
async saveAccount(origin, account) {
|
|
2901
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
2902
|
+
let validated;
|
|
2903
|
+
try {
|
|
2904
|
+
validated = skillAccountSchema.parse(account);
|
|
2905
|
+
} catch {
|
|
2906
|
+
throw configurationConflict("SAG account is invalid", this.filePath);
|
|
2907
|
+
}
|
|
2908
|
+
await this.#withLock(async () => {
|
|
2909
|
+
const config = await this.load();
|
|
2910
|
+
config.accounts[normalizedOrigin] = validated;
|
|
2911
|
+
config.currentOrigin = normalizedOrigin;
|
|
2912
|
+
await this.#saveUnlocked(config);
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
async removeAccount(origin) {
|
|
2916
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
2917
|
+
await this.#withLock(async () => {
|
|
2918
|
+
const config = await this.load();
|
|
2919
|
+
delete config.accounts[normalizedOrigin];
|
|
2920
|
+
if (config.currentOrigin === normalizedOrigin) {
|
|
2921
|
+
config.currentOrigin = Object.keys(config.accounts).sort()[0];
|
|
2922
|
+
}
|
|
2923
|
+
await this.#saveUnlocked(config);
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
async setCurrentOrigin(origin) {
|
|
2927
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
2928
|
+
await this.#withLock(async () => {
|
|
2929
|
+
const config = await this.load();
|
|
2930
|
+
config.currentOrigin = normalizedOrigin;
|
|
2931
|
+
await this.#saveUnlocked(config);
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
async getAccount(origin) {
|
|
2935
|
+
return (await this.load()).accounts[normalizeSkillOrigin(origin)] ?? null;
|
|
2936
|
+
}
|
|
2937
|
+
async saveRecentKnowledgeBase(origin, knowledgeBase) {
|
|
2938
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
2939
|
+
const validated = recentKnowledgeBaseSchema.parse(knowledgeBase);
|
|
2940
|
+
await this.#withLock(async () => {
|
|
2941
|
+
const config = await this.load();
|
|
2942
|
+
const account = config.accounts[normalizedOrigin];
|
|
2943
|
+
if (!account) {
|
|
2944
|
+
throw configurationConflict(
|
|
2945
|
+
"SAG account authorization is required",
|
|
2946
|
+
this.filePath
|
|
2947
|
+
);
|
|
2948
|
+
}
|
|
2949
|
+
config.accounts[normalizedOrigin] = {
|
|
2950
|
+
...account,
|
|
2951
|
+
recentKnowledgeBase: validated
|
|
2952
|
+
};
|
|
2953
|
+
config.currentOrigin = normalizedOrigin;
|
|
2954
|
+
await this.#saveUnlocked(config);
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
async withRefreshLock(origin, operation) {
|
|
2958
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
2959
|
+
const digest2 = createHash2("sha256").update(normalizedOrigin).digest("hex");
|
|
2960
|
+
await mkdir8(path11.dirname(this.filePath), { recursive: true });
|
|
2961
|
+
let release;
|
|
2962
|
+
try {
|
|
2963
|
+
release = await lock7(`${this.filePath}.refresh-${digest2}`, {
|
|
2964
|
+
realpath: false,
|
|
2965
|
+
stale: 1e4,
|
|
2966
|
+
update: 2e3,
|
|
2967
|
+
retries: { retries: 300, factor: 1, minTimeout: 100, maxTimeout: 100 }
|
|
2968
|
+
});
|
|
2969
|
+
} catch {
|
|
2970
|
+
throw new CliError("CONFIG_CONFLICT", "SAG account refresh is busy", {
|
|
2971
|
+
exitCode: exitCodes.configConflict,
|
|
2972
|
+
hint: "Wait for the other SAG Skill refresh to finish and retry."
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
try {
|
|
2976
|
+
return await operation();
|
|
2977
|
+
} finally {
|
|
2978
|
+
await release();
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
async #withLock(operation) {
|
|
2982
|
+
await mkdir8(path11.dirname(this.filePath), { recursive: true });
|
|
2983
|
+
let release;
|
|
2984
|
+
try {
|
|
2985
|
+
release = await lock7(this.filePath, {
|
|
2986
|
+
realpath: false,
|
|
2987
|
+
stale: 1e4,
|
|
2988
|
+
update: 2e3,
|
|
2989
|
+
retries: { retries: 100, factor: 1, minTimeout: 10, maxTimeout: 10 }
|
|
2990
|
+
});
|
|
2991
|
+
} catch {
|
|
2992
|
+
throw new CliError("CONFIG_CONFLICT", "SAG account configuration is busy", {
|
|
2993
|
+
exitCode: exitCodes.configConflict,
|
|
2994
|
+
hint: "Wait for the other SAG Skill operation to finish and retry."
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
try {
|
|
2998
|
+
return await operation();
|
|
2999
|
+
} finally {
|
|
3000
|
+
await release();
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
async #saveUnlocked(config) {
|
|
3004
|
+
let validated;
|
|
3005
|
+
try {
|
|
3006
|
+
validated = skillAccountConfigSchema.parse(config);
|
|
3007
|
+
} catch {
|
|
3008
|
+
throw configurationConflict(
|
|
3009
|
+
"SAG account configuration is invalid",
|
|
3010
|
+
this.filePath
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
3013
|
+
const directory = path11.dirname(this.filePath);
|
|
3014
|
+
await mkdir8(directory, { recursive: true });
|
|
3015
|
+
const temporaryPath = path11.join(
|
|
3016
|
+
directory,
|
|
3017
|
+
`.${path11.basename(this.filePath)}.${randomUUID5()}.tmp`
|
|
3018
|
+
);
|
|
3019
|
+
let temporaryCreated = false;
|
|
3020
|
+
try {
|
|
3021
|
+
const handle = await open4(temporaryPath, "wx", 384);
|
|
3022
|
+
temporaryCreated = true;
|
|
3023
|
+
try {
|
|
3024
|
+
await handle.chmod(384).catch(() => void 0);
|
|
3025
|
+
await handle.writeFile(JSON.stringify(validated), "utf8");
|
|
3026
|
+
} finally {
|
|
3027
|
+
await handle.close();
|
|
3028
|
+
}
|
|
3029
|
+
await rename9(temporaryPath, this.filePath);
|
|
3030
|
+
} catch (error) {
|
|
3031
|
+
if (temporaryCreated) await unlink9(temporaryPath).catch(() => void 0);
|
|
3032
|
+
throw error;
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
|
|
3037
|
+
// src/skill/model.ts
|
|
3038
|
+
var SkillError = class extends Error {
|
|
3039
|
+
code;
|
|
3040
|
+
details;
|
|
3041
|
+
exitCode;
|
|
3042
|
+
problemCode;
|
|
3043
|
+
constructor(code, message, options) {
|
|
3044
|
+
super(message);
|
|
3045
|
+
this.name = "SkillError";
|
|
3046
|
+
this.code = code;
|
|
3047
|
+
this.details = options.details;
|
|
3048
|
+
this.exitCode = options.exitCode;
|
|
3049
|
+
this.problemCode = options.problemCode;
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
|
|
3053
|
+
// src/skill/account-auth-client.ts
|
|
3054
|
+
var dateTimeSchema = z12.string().datetime({ offset: true });
|
|
3055
|
+
var sessionIdSchema = z12.uuid();
|
|
3056
|
+
var createSessionSchema = z12.strictObject({
|
|
3057
|
+
session_id: sessionIdSchema,
|
|
3058
|
+
verification_url: z12.url(),
|
|
3059
|
+
expires_at: dateTimeSchema
|
|
3060
|
+
});
|
|
3061
|
+
var loginSessionSchema = z12.strictObject({
|
|
3062
|
+
session_id: sessionIdSchema,
|
|
3063
|
+
status: z12.enum(["pending", "approved", "denied", "consumed"]),
|
|
3064
|
+
expires_at: dateTimeSchema
|
|
3065
|
+
});
|
|
3066
|
+
var accountSchema = z12.strictObject({
|
|
3067
|
+
access_token: z12.string().min(1),
|
|
3068
|
+
refresh_token: z12.string().min(1),
|
|
3069
|
+
access_expires_at: dateTimeSchema,
|
|
3070
|
+
refresh_expires_at: dateTimeSchema,
|
|
3071
|
+
user: z12.strictObject({ id: z12.string().min(1), name: z12.string().min(1) })
|
|
3072
|
+
});
|
|
3073
|
+
var problemSchema = z12.object({ code: z12.string().min(1) }).passthrough();
|
|
3074
|
+
var SkillAccountAuthHttpClient = class {
|
|
3075
|
+
#fetch;
|
|
3076
|
+
#timeoutMs;
|
|
3077
|
+
constructor(options = {}) {
|
|
3078
|
+
this.#fetch = options.fetchImplementation ?? fetch;
|
|
3079
|
+
this.#timeoutMs = options.timeoutMs ?? 1e4;
|
|
3080
|
+
}
|
|
3081
|
+
async createLoginSession(origin, pkceChallenge) {
|
|
3082
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
3083
|
+
const response = await this.#request(
|
|
3084
|
+
normalizedOrigin,
|
|
3085
|
+
"/api/v1/integrations/skill-account-logins",
|
|
3086
|
+
{ method: "POST", body: { pkce_challenge: pkceChallenge } }
|
|
3087
|
+
);
|
|
3088
|
+
const parsed = await this.#parse(response, createSessionSchema);
|
|
3089
|
+
if (normalizeSkillOrigin(new URL(parsed.verification_url).origin) !== normalizedOrigin) {
|
|
3090
|
+
throw invalidResponse();
|
|
3091
|
+
}
|
|
3092
|
+
return {
|
|
3093
|
+
sessionId: parsed.session_id,
|
|
3094
|
+
verificationUrl: parsed.verification_url,
|
|
3095
|
+
expiresAt: parsed.expires_at
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
async getLoginSession(origin, sessionId) {
|
|
3099
|
+
const validatedId = this.#sessionId(sessionId);
|
|
3100
|
+
const response = await this.#request(
|
|
3101
|
+
normalizeSkillOrigin(origin),
|
|
3102
|
+
`/api/v1/integrations/skill-account-logins/${encodeURIComponent(validatedId)}`,
|
|
3103
|
+
{ method: "GET" }
|
|
3104
|
+
);
|
|
3105
|
+
const parsed = await this.#parse(response, loginSessionSchema);
|
|
3106
|
+
if (parsed.session_id !== validatedId) throw invalidResponse();
|
|
3107
|
+
return {
|
|
3108
|
+
sessionId: parsed.session_id,
|
|
3109
|
+
status: parsed.status,
|
|
3110
|
+
expiresAt: parsed.expires_at
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
async exchange(origin, input2) {
|
|
3114
|
+
const sessionId = this.#sessionId(input2.sessionId);
|
|
3115
|
+
const response = await this.#request(
|
|
3116
|
+
normalizeSkillOrigin(origin),
|
|
3117
|
+
`/api/v1/integrations/skill-account-logins/${encodeURIComponent(sessionId)}/exchange`,
|
|
3118
|
+
{ method: "POST", body: { pkce_verifier: input2.pkceVerifier } }
|
|
3119
|
+
);
|
|
3120
|
+
return asAccount(await this.#parse(response, accountSchema));
|
|
3121
|
+
}
|
|
3122
|
+
async refresh(origin, refreshToken) {
|
|
3123
|
+
const response = await this.#request(
|
|
3124
|
+
normalizeSkillOrigin(origin),
|
|
3125
|
+
"/api/v1/integrations/skill-account-sessions/refresh",
|
|
3126
|
+
{ method: "POST", body: { refresh_token: refreshToken } }
|
|
3127
|
+
);
|
|
3128
|
+
return asAccount(await this.#parse(response, accountSchema));
|
|
3129
|
+
}
|
|
3130
|
+
async logout(origin, accessToken, refreshToken) {
|
|
3131
|
+
const response = await this.#request(
|
|
3132
|
+
normalizeSkillOrigin(origin),
|
|
3133
|
+
"/api/v1/integrations/skill-account-sessions/current",
|
|
3134
|
+
{
|
|
3135
|
+
method: "DELETE",
|
|
3136
|
+
body: { refresh_token: refreshToken },
|
|
3137
|
+
authorization: `Bearer ${accessToken}`
|
|
3138
|
+
}
|
|
3139
|
+
);
|
|
3140
|
+
if (response.status !== 204) throw invalidResponse();
|
|
3141
|
+
}
|
|
3142
|
+
#sessionId(value) {
|
|
3143
|
+
const parsed = sessionIdSchema.safeParse(value);
|
|
3144
|
+
if (!parsed.success) {
|
|
3145
|
+
throw new SkillError("INVALID_ARGUMENT", "Skill login session is invalid", {
|
|
3146
|
+
exitCode: exitCodes.invalidArgument
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
return parsed.data;
|
|
3150
|
+
}
|
|
3151
|
+
async #request(origin, requestPath, options) {
|
|
3152
|
+
let response;
|
|
3153
|
+
try {
|
|
3154
|
+
response = await this.#fetch(new URL(requestPath, origin), {
|
|
3155
|
+
headers: {
|
|
3156
|
+
Accept: "application/json",
|
|
3157
|
+
"Cache-Control": "no-store",
|
|
3158
|
+
...options.body ? { "Content-Type": "application/json" } : {},
|
|
3159
|
+
...options.authorization ? { Authorization: options.authorization } : {}
|
|
3160
|
+
},
|
|
3161
|
+
method: options.method,
|
|
3162
|
+
redirect: "error",
|
|
3163
|
+
signal: AbortSignal.timeout(this.#timeoutMs),
|
|
3164
|
+
...options.body ? { body: JSON.stringify(options.body) } : {}
|
|
3165
|
+
});
|
|
3166
|
+
} catch {
|
|
3167
|
+
throw new SkillError(
|
|
3168
|
+
"NETWORK_UNREACHABLE",
|
|
3169
|
+
"Cannot reach the SAG account authorization service",
|
|
3170
|
+
{ exitCode: exitCodes.networkUnreachable }
|
|
3171
|
+
);
|
|
3172
|
+
}
|
|
3173
|
+
if (response.redirected || response.status >= 300 && response.status < 400) {
|
|
3174
|
+
throw invalidResponse();
|
|
3175
|
+
}
|
|
3176
|
+
if (!response.ok) {
|
|
3177
|
+
let code = "unknown";
|
|
3178
|
+
try {
|
|
3179
|
+
const problem = problemSchema.safeParse(await response.json());
|
|
3180
|
+
if (problem.success) code = problem.data.code;
|
|
3181
|
+
} catch {
|
|
3182
|
+
}
|
|
3183
|
+
throw problemError(code, response.status);
|
|
3184
|
+
}
|
|
3185
|
+
return response;
|
|
3186
|
+
}
|
|
3187
|
+
async #parse(response, schema) {
|
|
3188
|
+
try {
|
|
3189
|
+
const parsed = schema.safeParse(await response.json());
|
|
3190
|
+
if (parsed.success) return parsed.data;
|
|
3191
|
+
} catch {
|
|
3192
|
+
}
|
|
3193
|
+
throw invalidResponse();
|
|
3194
|
+
}
|
|
3195
|
+
};
|
|
3196
|
+
function asAccount(value) {
|
|
3197
|
+
return {
|
|
3198
|
+
accessToken: value.access_token,
|
|
3199
|
+
refreshToken: value.refresh_token,
|
|
3200
|
+
accessExpiresAt: value.access_expires_at,
|
|
3201
|
+
refreshExpiresAt: value.refresh_expires_at,
|
|
3202
|
+
user: value.user
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
function invalidResponse() {
|
|
3206
|
+
return new SkillError(
|
|
3207
|
+
"INVALID_RESPONSE",
|
|
3208
|
+
"SAG account authorization returned an incompatible response",
|
|
3209
|
+
{ exitCode: exitCodes.internalError }
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
function problemError(code, status2) {
|
|
3213
|
+
if (code === "access_denied" || status2 === 403) {
|
|
3214
|
+
return new SkillError("PERMISSION_DENIED", "SAG account authorization was denied", {
|
|
3215
|
+
exitCode: exitCodes.permissionDenied,
|
|
3216
|
+
problemCode: "access_denied"
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
if (code === "login_session_expired" || code === "invalid_grant" || status2 === 410) {
|
|
3220
|
+
return new SkillError("AUTH_EXPIRED", "SAG account authorization has expired", {
|
|
3221
|
+
exitCode: exitCodes.authRequired,
|
|
3222
|
+
problemCode: code
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
if (status2 === 401) {
|
|
3226
|
+
return new SkillError("AUTH_REQUIRED", "SAG account authorization is required", {
|
|
3227
|
+
exitCode: exitCodes.authRequired
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
if (status2 === 429 || code === "rate_limited") {
|
|
3231
|
+
return new SkillError("RATE_LIMITED", "SAG account authorization is rate limited", {
|
|
3232
|
+
exitCode: exitCodes.networkUnreachable,
|
|
3233
|
+
problemCode: "rate_limited"
|
|
3234
|
+
});
|
|
3235
|
+
}
|
|
3236
|
+
return invalidResponse();
|
|
3237
|
+
}
|
|
3238
|
+
|
|
2684
3239
|
// src/program.ts
|
|
3240
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
2685
3241
|
import { Command, CommanderError, Option } from "commander";
|
|
2686
3242
|
|
|
2687
3243
|
// src/api/schemas.ts
|
|
2688
|
-
import { z as
|
|
2689
|
-
var dateTime =
|
|
2690
|
-
var rootSchema =
|
|
2691
|
-
name:
|
|
2692
|
-
version:
|
|
2693
|
-
docs:
|
|
3244
|
+
import { z as z13 } from "zod";
|
|
3245
|
+
var dateTime = z13.string().min(1);
|
|
3246
|
+
var rootSchema = z13.object({
|
|
3247
|
+
name: z13.string().min(1),
|
|
3248
|
+
version: z13.string().min(1),
|
|
3249
|
+
docs: z13.string()
|
|
2694
3250
|
}).passthrough();
|
|
2695
|
-
var readySchema =
|
|
2696
|
-
status:
|
|
2697
|
-
db:
|
|
3251
|
+
var readySchema = z13.object({
|
|
3252
|
+
status: z13.string(),
|
|
3253
|
+
db: z13.boolean()
|
|
2698
3254
|
}).passthrough();
|
|
2699
|
-
var capabilitiesSchema =
|
|
2700
|
-
var userSchema =
|
|
2701
|
-
id:
|
|
2702
|
-
email:
|
|
2703
|
-
name:
|
|
3255
|
+
var capabilitiesSchema = z13.record(z13.string(), z13.unknown());
|
|
3256
|
+
var userSchema = z13.object({
|
|
3257
|
+
id: z13.string().min(1),
|
|
3258
|
+
email: z13.string(),
|
|
3259
|
+
name: z13.string(),
|
|
2704
3260
|
created_at: dateTime.optional()
|
|
2705
3261
|
}).passthrough();
|
|
2706
|
-
var loginResponseSchema =
|
|
2707
|
-
access_token:
|
|
3262
|
+
var loginResponseSchema = z13.object({
|
|
3263
|
+
access_token: z13.string().min(1),
|
|
2708
3264
|
user: userSchema
|
|
2709
3265
|
}).passthrough();
|
|
2710
|
-
var sourceSchema =
|
|
2711
|
-
id:
|
|
2712
|
-
name:
|
|
2713
|
-
description:
|
|
2714
|
-
source_type:
|
|
2715
|
-
connector_kind:
|
|
2716
|
-
status:
|
|
2717
|
-
document_count:
|
|
2718
|
-
chunk_count:
|
|
2719
|
-
event_count:
|
|
3266
|
+
var sourceSchema = z13.object({
|
|
3267
|
+
id: z13.string().min(1),
|
|
3268
|
+
name: z13.string(),
|
|
3269
|
+
description: z13.string(),
|
|
3270
|
+
source_type: z13.enum(["document", "web", "message", "audio"]),
|
|
3271
|
+
connector_kind: z13.string(),
|
|
3272
|
+
status: z13.enum(["active", "paused", "error"]),
|
|
3273
|
+
document_count: z13.number().int().nonnegative(),
|
|
3274
|
+
chunk_count: z13.number().int().nonnegative(),
|
|
3275
|
+
event_count: z13.number().int().nonnegative(),
|
|
2720
3276
|
created_at: dateTime,
|
|
2721
3277
|
updated_at: dateTime
|
|
2722
3278
|
}).passthrough();
|
|
2723
|
-
var documentStatusSchema =
|
|
3279
|
+
var documentStatusSchema = z13.enum([
|
|
2724
3280
|
"pending",
|
|
2725
3281
|
"loading",
|
|
2726
3282
|
"extracting",
|
|
@@ -2728,102 +3284,102 @@ var documentStatusSchema = z10.enum([
|
|
|
2728
3284
|
"ready",
|
|
2729
3285
|
"failed"
|
|
2730
3286
|
]);
|
|
2731
|
-
var documentSchema =
|
|
2732
|
-
id:
|
|
2733
|
-
source_id:
|
|
2734
|
-
filename:
|
|
2735
|
-
content_type:
|
|
2736
|
-
size_bytes:
|
|
3287
|
+
var documentSchema = z13.object({
|
|
3288
|
+
id: z13.string().min(1),
|
|
3289
|
+
source_id: z13.string().min(1),
|
|
3290
|
+
filename: z13.string(),
|
|
3291
|
+
content_type: z13.string(),
|
|
3292
|
+
size_bytes: z13.number().int().nonnegative(),
|
|
2737
3293
|
status: documentStatusSchema,
|
|
2738
|
-
chunk_count:
|
|
2739
|
-
event_count:
|
|
2740
|
-
progress:
|
|
2741
|
-
token_usage:
|
|
2742
|
-
error:
|
|
3294
|
+
chunk_count: z13.number().int().nonnegative(),
|
|
3295
|
+
event_count: z13.number().int().nonnegative(),
|
|
3296
|
+
progress: z13.number().int().min(0).max(100),
|
|
3297
|
+
token_usage: z13.number().int().nonnegative(),
|
|
3298
|
+
error: z13.string().nullable(),
|
|
2743
3299
|
created_at: dateTime,
|
|
2744
3300
|
updated_at: dateTime
|
|
2745
3301
|
}).passthrough();
|
|
2746
|
-
var searchSectionSchema =
|
|
2747
|
-
chunk_id:
|
|
2748
|
-
heading:
|
|
2749
|
-
content:
|
|
2750
|
-
score:
|
|
2751
|
-
rank:
|
|
2752
|
-
source_id:
|
|
2753
|
-
source_name:
|
|
3302
|
+
var searchSectionSchema = z13.object({
|
|
3303
|
+
chunk_id: z13.string().nullable(),
|
|
3304
|
+
heading: z13.string(),
|
|
3305
|
+
content: z13.string(),
|
|
3306
|
+
score: z13.number(),
|
|
3307
|
+
rank: z13.number().int(),
|
|
3308
|
+
source_id: z13.string().nullable(),
|
|
3309
|
+
source_name: z13.string().nullable().optional()
|
|
2754
3310
|
}).passthrough();
|
|
2755
|
-
var searchEventSchema =
|
|
2756
|
-
id:
|
|
2757
|
-
title:
|
|
2758
|
-
summary:
|
|
2759
|
-
rank:
|
|
2760
|
-
score:
|
|
3311
|
+
var searchEventSchema = z13.object({
|
|
3312
|
+
id: z13.string(),
|
|
3313
|
+
title: z13.string(),
|
|
3314
|
+
summary: z13.string(),
|
|
3315
|
+
rank: z13.number().int(),
|
|
3316
|
+
score: z13.number()
|
|
2761
3317
|
}).passthrough();
|
|
2762
|
-
var sourceHitSchema =
|
|
2763
|
-
source_id:
|
|
2764
|
-
source_name:
|
|
2765
|
-
event_hits:
|
|
2766
|
-
max_score:
|
|
2767
|
-
latest_event_time:
|
|
3318
|
+
var sourceHitSchema = z13.object({
|
|
3319
|
+
source_id: z13.string(),
|
|
3320
|
+
source_name: z13.string().nullable().optional(),
|
|
3321
|
+
event_hits: z13.number().int(),
|
|
3322
|
+
max_score: z13.number(),
|
|
3323
|
+
latest_event_time: z13.string().nullable().optional()
|
|
2768
3324
|
}).passthrough();
|
|
2769
|
-
var searchResponseSchema =
|
|
2770
|
-
query:
|
|
2771
|
-
sections:
|
|
2772
|
-
events:
|
|
2773
|
-
entities:
|
|
2774
|
-
relations:
|
|
2775
|
-
source_hits:
|
|
2776
|
-
summary:
|
|
2777
|
-
exploration_id:
|
|
2778
|
-
stats:
|
|
3325
|
+
var searchResponseSchema = z13.object({
|
|
3326
|
+
query: z13.string(),
|
|
3327
|
+
sections: z13.array(searchSectionSchema),
|
|
3328
|
+
events: z13.array(searchEventSchema),
|
|
3329
|
+
entities: z13.array(z13.record(z13.string(), z13.unknown())),
|
|
3330
|
+
relations: z13.array(z13.record(z13.string(), z13.unknown())),
|
|
3331
|
+
source_hits: z13.array(sourceHitSchema),
|
|
3332
|
+
summary: z13.string(),
|
|
3333
|
+
exploration_id: z13.string().nullable(),
|
|
3334
|
+
stats: z13.record(z13.string(), z13.unknown())
|
|
2779
3335
|
}).passthrough();
|
|
2780
|
-
var mcpDescriptorSchema =
|
|
2781
|
-
name:
|
|
2782
|
-
scope:
|
|
2783
|
-
source_count:
|
|
2784
|
-
tools:
|
|
2785
|
-
http:
|
|
2786
|
-
transport:
|
|
2787
|
-
url:
|
|
2788
|
-
headers:
|
|
3336
|
+
var mcpDescriptorSchema = z13.object({
|
|
3337
|
+
name: z13.string(),
|
|
3338
|
+
scope: z13.string(),
|
|
3339
|
+
source_count: z13.number().int().nonnegative(),
|
|
3340
|
+
tools: z13.array(z13.string()),
|
|
3341
|
+
http: z13.object({
|
|
3342
|
+
transport: z13.string(),
|
|
3343
|
+
url: z13.string().url(),
|
|
3344
|
+
headers: z13.record(z13.string(), z13.string())
|
|
2789
3345
|
}).passthrough()
|
|
2790
3346
|
}).passthrough();
|
|
2791
|
-
var outlineItemSchema =
|
|
2792
|
-
rank:
|
|
2793
|
-
heading:
|
|
2794
|
-
chunk_id:
|
|
3347
|
+
var outlineItemSchema = z13.object({
|
|
3348
|
+
rank: z13.number().int(),
|
|
3349
|
+
heading: z13.string(),
|
|
3350
|
+
chunk_id: z13.string()
|
|
2795
3351
|
}).passthrough();
|
|
2796
|
-
var outlineSchema =
|
|
2797
|
-
document_id:
|
|
2798
|
-
filename:
|
|
2799
|
-
outline:
|
|
3352
|
+
var outlineSchema = z13.object({
|
|
3353
|
+
document_id: z13.string(),
|
|
3354
|
+
filename: z13.string(),
|
|
3355
|
+
outline: z13.array(outlineItemSchema)
|
|
2800
3356
|
}).passthrough();
|
|
2801
|
-
var grepMatchSchema =
|
|
2802
|
-
chunk_id:
|
|
2803
|
-
heading:
|
|
2804
|
-
snippet:
|
|
3357
|
+
var grepMatchSchema = z13.object({
|
|
3358
|
+
chunk_id: z13.string(),
|
|
3359
|
+
heading: z13.string(),
|
|
3360
|
+
snippet: z13.string()
|
|
2805
3361
|
}).passthrough();
|
|
2806
|
-
var grepResponseSchema =
|
|
2807
|
-
pattern:
|
|
2808
|
-
matches:
|
|
2809
|
-
count:
|
|
3362
|
+
var grepResponseSchema = z13.object({
|
|
3363
|
+
pattern: z13.string(),
|
|
3364
|
+
matches: z13.array(grepMatchSchema),
|
|
3365
|
+
count: z13.number().int().nonnegative()
|
|
2810
3366
|
}).passthrough();
|
|
2811
|
-
var readResponseSchema =
|
|
2812
|
-
document_id:
|
|
2813
|
-
filename:
|
|
2814
|
-
total_lines:
|
|
2815
|
-
offset:
|
|
2816
|
-
limit:
|
|
2817
|
-
lines:
|
|
3367
|
+
var readResponseSchema = z13.object({
|
|
3368
|
+
document_id: z13.string(),
|
|
3369
|
+
filename: z13.string(),
|
|
3370
|
+
total_lines: z13.number().int().nonnegative(),
|
|
3371
|
+
offset: z13.number().int().positive(),
|
|
3372
|
+
limit: z13.number().int().positive(),
|
|
3373
|
+
lines: z13.array(z13.string())
|
|
2818
3374
|
}).passthrough();
|
|
2819
|
-
var entityContextSchema =
|
|
2820
|
-
entity_id:
|
|
2821
|
-
name:
|
|
2822
|
-
type:
|
|
2823
|
-
description:
|
|
2824
|
-
context:
|
|
2825
|
-
source_id:
|
|
2826
|
-
source_name:
|
|
3375
|
+
var entityContextSchema = z13.object({
|
|
3376
|
+
entity_id: z13.string(),
|
|
3377
|
+
name: z13.string(),
|
|
3378
|
+
type: z13.string(),
|
|
3379
|
+
description: z13.string(),
|
|
3380
|
+
context: z13.string(),
|
|
3381
|
+
source_id: z13.string(),
|
|
3382
|
+
source_name: z13.string()
|
|
2827
3383
|
}).passthrough();
|
|
2828
3384
|
|
|
2829
3385
|
// src/api/client.ts
|
|
@@ -2932,7 +3488,7 @@ var SagClient = class {
|
|
|
2932
3488
|
entityContextSchema
|
|
2933
3489
|
);
|
|
2934
3490
|
}
|
|
2935
|
-
async #request(
|
|
3491
|
+
async #request(path16, schema, options = {}) {
|
|
2936
3492
|
const authenticated = options.authenticated ?? true;
|
|
2937
3493
|
if (authenticated && !this.#token) {
|
|
2938
3494
|
throw new CliError("AUTH_REQUIRED", "No SAG token is configured", {
|
|
@@ -2952,7 +3508,7 @@ var SagClient = class {
|
|
|
2952
3508
|
}
|
|
2953
3509
|
let response;
|
|
2954
3510
|
try {
|
|
2955
|
-
response = await this.#fetch(new URL(
|
|
3511
|
+
response = await this.#fetch(new URL(path16, `${this.#origin}/`), {
|
|
2956
3512
|
method: options.method ?? "GET",
|
|
2957
3513
|
headers,
|
|
2958
3514
|
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
|
|
@@ -3532,9 +4088,9 @@ async function agentStatus(runtime2, agent, serverName) {
|
|
|
3532
4088
|
}
|
|
3533
4089
|
}
|
|
3534
4090
|
async function statusLocalAgents(runtime2, input2) {
|
|
3535
|
-
const
|
|
4091
|
+
const agents2 = input2.agent ? [input2.agent] : ["codex", "claude-code"];
|
|
3536
4092
|
return Promise.all(
|
|
3537
|
-
|
|
4093
|
+
agents2.map((agent) => agentStatus(runtime2, agent, input2.serverName))
|
|
3538
4094
|
);
|
|
3539
4095
|
}
|
|
3540
4096
|
async function listAgentHosts(runtime2) {
|
|
@@ -3829,7 +4385,7 @@ function credentialStoreForEnterpriseMcpConnection(connection, stores) {
|
|
|
3829
4385
|
}
|
|
3830
4386
|
|
|
3831
4387
|
// src/mcp/enterprise/import.ts
|
|
3832
|
-
import { createHash as
|
|
4388
|
+
import { createHash as createHash3 } from "crypto";
|
|
3833
4389
|
function invalidImport(message, cause) {
|
|
3834
4390
|
return new CliError("INVALID_ARGUMENT", message, {
|
|
3835
4391
|
exitCode: exitCodes.invalidArgument,
|
|
@@ -3958,7 +4514,7 @@ function selectEnterpriseMcpCandidates(servers, requested = []) {
|
|
|
3958
4514
|
return selectByServerKey(servers, requested);
|
|
3959
4515
|
}
|
|
3960
4516
|
function connectionIdForCollision(serverKey, url) {
|
|
3961
|
-
return `${serverKey}-${
|
|
4517
|
+
return `${serverKey}-${createHash3("sha256").update(new URL(url).toString()).digest("hex").slice(0, 8)}`;
|
|
3962
4518
|
}
|
|
3963
4519
|
|
|
3964
4520
|
// src/mcp/enterprise/runtime.ts
|
|
@@ -3997,7 +4553,7 @@ import { isDeepStrictEqual } from "util";
|
|
|
3997
4553
|
function stateId(agent, connectionId) {
|
|
3998
4554
|
return connectionKey(agent, "user", connectionId);
|
|
3999
4555
|
}
|
|
4000
|
-
function
|
|
4556
|
+
function configurationConflict2(message) {
|
|
4001
4557
|
return new CliError("CONFIG_CONFLICT", message, {
|
|
4002
4558
|
exitCode: exitCodes.configConflict,
|
|
4003
4559
|
hint: "\u8BF7\u68C0\u67E5\u8BE5 Agent \u7684\u540C\u540D MCP \u914D\u7F6E\u540E\u91CD\u8BD5\u3002"
|
|
@@ -4078,7 +4634,7 @@ async function attachEnterpriseMcp(runtime2, input2) {
|
|
|
4078
4634
|
connectionId: input2.connection.id
|
|
4079
4635
|
});
|
|
4080
4636
|
if (action === "blocked-drift") {
|
|
4081
|
-
throw
|
|
4637
|
+
throw configurationConflict2("\u53D7\u7BA1 Agent MCP \u914D\u7F6E\u5DF2\u5728 SAG CLI \u4E4B\u5916\u88AB\u4FEE\u6539");
|
|
4082
4638
|
}
|
|
4083
4639
|
if (action === "unchanged") {
|
|
4084
4640
|
return success(input2.agent, "skipped", "Agent \u4E2D\u7684\u4F01\u4E1A MCP \u914D\u7F6E\u5DF2\u7ECF\u662F\u6700\u65B0\u72B6\u6001\u3002");
|
|
@@ -4100,7 +4656,7 @@ async function attachEnterpriseMcp(runtime2, input2) {
|
|
|
4100
4656
|
await adapter.add(input2.connection.id, "user", input2.spec);
|
|
4101
4657
|
const readBack = await adapter.read(input2.connection.id, "user");
|
|
4102
4658
|
if (!readBack || fingerprintConnection(readBack.spec) !== fingerprintConnection(input2.spec)) {
|
|
4103
|
-
throw
|
|
4659
|
+
throw configurationConflict2("Agent MCP \u56DE\u8BFB\u7ED3\u679C\u4E0E\u8BF7\u6C42\u7684\u8FDE\u63A5\u4E0D\u4E00\u81F4");
|
|
4104
4660
|
}
|
|
4105
4661
|
await runtime2.verifier.verify(readBack.spec, { timeoutMs: input2.timeoutMs });
|
|
4106
4662
|
await runtime2.managed.setAgent(
|
|
@@ -4146,10 +4702,10 @@ async function detachEnterpriseMcp(runtime2, input2) {
|
|
|
4146
4702
|
runtime2.managed.getAgent(id)
|
|
4147
4703
|
]);
|
|
4148
4704
|
if (!managed || managed.connectionId !== input2.connection.id || !host) {
|
|
4149
|
-
throw
|
|
4705
|
+
throw configurationConflict2("\u8BE5 Agent MCP \u914D\u7F6E\u4E0D\u53D7\u5F53\u524D\u4F01\u4E1A\u8FDE\u63A5\u7BA1\u7406");
|
|
4150
4706
|
}
|
|
4151
4707
|
if (host.fingerprint !== managed.fingerprint) {
|
|
4152
|
-
throw
|
|
4708
|
+
throw configurationConflict2("\u53D7\u7BA1 Agent MCP \u914D\u7F6E\u5DF2\u5728 SAG CLI \u4E4B\u5916\u88AB\u4FEE\u6539");
|
|
4153
4709
|
}
|
|
4154
4710
|
if (input2.dryRun) {
|
|
4155
4711
|
return success(
|
|
@@ -4187,7 +4743,7 @@ async function refreshManagedAgents(runtime2, input2) {
|
|
|
4187
4743
|
return Promise.all(
|
|
4188
4744
|
matching.map(async (state) => {
|
|
4189
4745
|
const agent = agentForState(state, input2.connection.id);
|
|
4190
|
-
if (!agent) return failed(state.id,
|
|
4746
|
+
if (!agent) return failed(state.id, configurationConflict2("\u53D7\u7BA1 Agent \u72B6\u6001\u65E0\u6548"));
|
|
4191
4747
|
try {
|
|
4192
4748
|
return await attachEnterpriseMcp(runtime2, { ...input2, agent, yes: true });
|
|
4193
4749
|
} catch (cause) {
|
|
@@ -4198,10 +4754,10 @@ async function refreshManagedAgents(runtime2, input2) {
|
|
|
4198
4754
|
}
|
|
4199
4755
|
|
|
4200
4756
|
// src/mcp/enterprise/skill-service.ts
|
|
4201
|
-
import { createHash as
|
|
4202
|
-
import { cp, lstat as lstat2, mkdir as
|
|
4757
|
+
import { createHash as createHash4 } from "crypto";
|
|
4758
|
+
import { cp, lstat as lstat2, mkdir as mkdir9, readFile as readFile11, readdir, rename as rename10, rm } from "fs/promises";
|
|
4203
4759
|
import { fileURLToPath } from "url";
|
|
4204
|
-
import
|
|
4760
|
+
import path12 from "path";
|
|
4205
4761
|
var skillName = "sag-mcp";
|
|
4206
4762
|
var skillDescription = "Use connected enterprise SAG MCP servers to search, read, and manage authorized knowledge.";
|
|
4207
4763
|
var requiredReferences = [
|
|
@@ -4211,32 +4767,32 @@ var requiredReferences = [
|
|
|
4211
4767
|
function skillTarget(agent, options) {
|
|
4212
4768
|
switch (agent) {
|
|
4213
4769
|
case "codex":
|
|
4214
|
-
return
|
|
4215
|
-
options.CODEX_HOME ||
|
|
4770
|
+
return path12.join(
|
|
4771
|
+
options.CODEX_HOME || path12.join(options.home, ".codex"),
|
|
4216
4772
|
"skills",
|
|
4217
4773
|
skillName
|
|
4218
4774
|
);
|
|
4219
4775
|
case "claude-code":
|
|
4220
|
-
return
|
|
4776
|
+
return path12.join(options.home, ".claude", "skills", skillName);
|
|
4221
4777
|
case "workbuddy":
|
|
4222
|
-
return
|
|
4778
|
+
return path12.join(options.home, ".workbuddy", "skills", skillName);
|
|
4223
4779
|
}
|
|
4224
4780
|
}
|
|
4225
4781
|
function managedSkillId(agent) {
|
|
4226
4782
|
return `${agent}:${skillName}`;
|
|
4227
4783
|
}
|
|
4228
4784
|
function bundledSkillPath() {
|
|
4229
|
-
const moduleDirectory =
|
|
4230
|
-
const packageRoot =
|
|
4231
|
-
return
|
|
4785
|
+
const moduleDirectory = path12.dirname(fileURLToPath(import.meta.url));
|
|
4786
|
+
const packageRoot = path12.basename(moduleDirectory) === "dist" ? path12.dirname(moduleDirectory) : path12.resolve(moduleDirectory, "../../..");
|
|
4787
|
+
return path12.join(packageRoot, "skills", skillName);
|
|
4232
4788
|
}
|
|
4233
4789
|
function timestamp(now) {
|
|
4234
4790
|
return now.toISOString().replace(/[-:.]/gu, "");
|
|
4235
4791
|
}
|
|
4236
4792
|
function siblingPath(target, prefix, now) {
|
|
4237
|
-
return
|
|
4238
|
-
|
|
4239
|
-
`${
|
|
4793
|
+
return path12.join(
|
|
4794
|
+
path12.dirname(target),
|
|
4795
|
+
`${path12.basename(target)}.${prefix}-${timestamp(now)}-${process.pid}`
|
|
4240
4796
|
);
|
|
4241
4797
|
}
|
|
4242
4798
|
async function exists(target) {
|
|
@@ -4249,26 +4805,26 @@ async function exists(target) {
|
|
|
4249
4805
|
}
|
|
4250
4806
|
}
|
|
4251
4807
|
async function validateSkillBundle(bundlePath) {
|
|
4252
|
-
const skill = await
|
|
4808
|
+
const skill = await readFile11(path12.join(bundlePath, "SKILL.md"), "utf8");
|
|
4253
4809
|
const lines = skill.split(/\r?\n/u);
|
|
4254
4810
|
const lineCount = skill.endsWith("\n") ? lines.length - 1 : lines.length;
|
|
4255
4811
|
if (lineCount >= 100 || lines[0] !== "---" || lines[1] !== `name: ${skillName}` || lines[2] !== `description: ${skillDescription}` || lines[3] !== "---") {
|
|
4256
4812
|
throw new Error("\u968F\u5305\u53D1\u5E03\u7684 SAG MCP Skill frontmatter \u65E0\u6548\u3002");
|
|
4257
4813
|
}
|
|
4258
4814
|
for (const reference of requiredReferences) {
|
|
4259
|
-
await
|
|
4815
|
+
await readFile11(path12.join(bundlePath, reference), "utf8");
|
|
4260
4816
|
if (!skill.includes(`(${reference})`)) {
|
|
4261
4817
|
throw new Error(`\u968F\u5305\u53D1\u5E03\u7684 SAG MCP Skill \u672A\u5F15\u7528 ${reference}\u3002`);
|
|
4262
4818
|
}
|
|
4263
4819
|
}
|
|
4264
4820
|
}
|
|
4265
4821
|
async function updateDirectoryFingerprint(hash, root, relative = "") {
|
|
4266
|
-
const current =
|
|
4822
|
+
const current = path12.join(root, relative);
|
|
4267
4823
|
const entries = await readdir(current, { withFileTypes: true });
|
|
4268
4824
|
for (const entry of entries.sort(
|
|
4269
4825
|
(left, right) => left.name.localeCompare(right.name)
|
|
4270
4826
|
)) {
|
|
4271
|
-
const entryRelative =
|
|
4827
|
+
const entryRelative = path12.join(relative, entry.name);
|
|
4272
4828
|
if (entry.isDirectory()) {
|
|
4273
4829
|
hash.update(`directory:${entryRelative}
|
|
4274
4830
|
`);
|
|
@@ -4280,12 +4836,12 @@ async function updateDirectoryFingerprint(hash, root, relative = "") {
|
|
|
4280
4836
|
}
|
|
4281
4837
|
hash.update(`file:${entryRelative}
|
|
4282
4838
|
`);
|
|
4283
|
-
hash.update(await
|
|
4839
|
+
hash.update(await readFile11(path12.join(root, entryRelative)));
|
|
4284
4840
|
}
|
|
4285
4841
|
}
|
|
4286
4842
|
async function fingerprintSkillBundle(bundlePath) {
|
|
4287
4843
|
await validateSkillBundle(bundlePath);
|
|
4288
|
-
const hash =
|
|
4844
|
+
const hash = createHash4("sha256");
|
|
4289
4845
|
await updateDirectoryFingerprint(hash, bundlePath);
|
|
4290
4846
|
return `sha256:${hash.digest("hex")}`;
|
|
4291
4847
|
}
|
|
@@ -4321,7 +4877,7 @@ async function restoreTarget(input2) {
|
|
|
4321
4877
|
if (!input2.targetMutated) return;
|
|
4322
4878
|
if (input2.hadTarget && input2.backupPath && await exists(input2.backupPath)) {
|
|
4323
4879
|
await rm(input2.target, { recursive: true, force: true });
|
|
4324
|
-
await
|
|
4880
|
+
await rename10(input2.backupPath, input2.target);
|
|
4325
4881
|
return;
|
|
4326
4882
|
}
|
|
4327
4883
|
if (!input2.hadTarget) {
|
|
@@ -4341,7 +4897,7 @@ async function installEnterpriseSkill(input2) {
|
|
|
4341
4897
|
try {
|
|
4342
4898
|
hadTarget = await exists(target);
|
|
4343
4899
|
await validateSkillBundle(bundlePath);
|
|
4344
|
-
await
|
|
4900
|
+
await mkdir9(path12.dirname(target), { recursive: true });
|
|
4345
4901
|
await cp(bundlePath, temporaryPath, {
|
|
4346
4902
|
recursive: true,
|
|
4347
4903
|
errorOnExist: true,
|
|
@@ -4350,10 +4906,10 @@ async function installEnterpriseSkill(input2) {
|
|
|
4350
4906
|
await validateSkillBundle(temporaryPath);
|
|
4351
4907
|
if (hadTarget) {
|
|
4352
4908
|
backupPath = siblingPath(target, "backup", input2.now());
|
|
4353
|
-
await
|
|
4909
|
+
await rename10(target, backupPath);
|
|
4354
4910
|
targetMutated = true;
|
|
4355
4911
|
}
|
|
4356
|
-
await
|
|
4912
|
+
await rename10(temporaryPath, target);
|
|
4357
4913
|
targetMutated = true;
|
|
4358
4914
|
const fingerprint = await fingerprintSkillBundle(target);
|
|
4359
4915
|
await input2.managed.setSkill({
|
|
@@ -4408,11 +4964,11 @@ async function uninstallEnterpriseSkill(input2) {
|
|
|
4408
4964
|
throw conflict5("\u53D7 SAG CLI \u7BA1\u7406\u7684 SAG MCP Skill \u5DF2\u88AB\u5916\u90E8\u4FEE\u6539\u3002");
|
|
4409
4965
|
}
|
|
4410
4966
|
const removedPath = siblingPath(target, "remove", input2.now());
|
|
4411
|
-
await
|
|
4967
|
+
await rename10(target, removedPath);
|
|
4412
4968
|
try {
|
|
4413
4969
|
await input2.managed.deleteSkill(state.id);
|
|
4414
4970
|
} catch (cause) {
|
|
4415
|
-
await
|
|
4971
|
+
await rename10(removedPath, target);
|
|
4416
4972
|
throw cause;
|
|
4417
4973
|
}
|
|
4418
4974
|
await rm(removedPath, { recursive: true, force: true }).catch(() => void 0);
|
|
@@ -4428,11 +4984,11 @@ async function uninstallEnterpriseSkill(input2) {
|
|
|
4428
4984
|
throw conflict5("\u6CA1\u6709\u53EF\u79FB\u9664\u7684 SAG MCP Skill\u3002");
|
|
4429
4985
|
}
|
|
4430
4986
|
const backupPath = siblingPath(target, "backup", input2.now());
|
|
4431
|
-
await
|
|
4987
|
+
await rename10(target, backupPath);
|
|
4432
4988
|
try {
|
|
4433
4989
|
await input2.managed.deleteSkill(managedSkillId(input2.agent));
|
|
4434
4990
|
} catch (cause) {
|
|
4435
|
-
await
|
|
4991
|
+
await rename10(backupPath, target).catch(() => void 0);
|
|
4436
4992
|
throw cause;
|
|
4437
4993
|
}
|
|
4438
4994
|
return {
|
|
@@ -4844,7 +5400,7 @@ async function connectEnterpriseMcp(input2) {
|
|
|
4844
5400
|
);
|
|
4845
5401
|
}
|
|
4846
5402
|
}
|
|
4847
|
-
const
|
|
5403
|
+
const agents2 = verified.length ? await selectInstalledAgents(input2) : [];
|
|
4848
5404
|
const initialActive = await input2.runtime.store.active();
|
|
4849
5405
|
const connections = [];
|
|
4850
5406
|
const skillEligibleAgents = /* @__PURE__ */ new Set();
|
|
@@ -4952,19 +5508,19 @@ async function connectEnterpriseMcp(input2) {
|
|
|
4952
5508
|
spec: item.parsed.connection
|
|
4953
5509
|
});
|
|
4954
5510
|
}
|
|
4955
|
-
for (const agent of
|
|
5511
|
+
for (const agent of agents2) {
|
|
4956
5512
|
stages.push(skippedAgentStage(agent));
|
|
4957
5513
|
zeroToolSkillAgents.add(agent);
|
|
4958
5514
|
}
|
|
4959
5515
|
continue;
|
|
4960
5516
|
}
|
|
4961
5517
|
let riskAccepted = input2.yes || input2.dryRun || !item.risk.hasWarning;
|
|
4962
|
-
if (!riskAccepted &&
|
|
5518
|
+
if (!riskAccepted && agents2.length) {
|
|
4963
5519
|
riskAccepted = await input2.runtime.agents.confirm(
|
|
4964
5520
|
`MCP ${connection.id} \u542B\u6709\u5199\u5165\u3001\u5220\u9664\u6216\u98CE\u9669\u672A\u77E5\u7684\u5DE5\u5177\uFF0C\u662F\u5426\u7EE7\u7EED\u63A5\u5165\u6240\u9009 Agent\uFF1F`
|
|
4965
5521
|
);
|
|
4966
5522
|
}
|
|
4967
|
-
for (const agent of
|
|
5523
|
+
for (const agent of agents2) {
|
|
4968
5524
|
if (!riskAccepted) {
|
|
4969
5525
|
stages.push(
|
|
4970
5526
|
stageSuccess(
|
|
@@ -4987,7 +5543,7 @@ async function connectEnterpriseMcp(input2) {
|
|
|
4987
5543
|
if (attached.stage.ok && attached.connected) successfulAgents.add(agent);
|
|
4988
5544
|
}
|
|
4989
5545
|
for (const agent of successfulAgents) {
|
|
4990
|
-
if (
|
|
5546
|
+
if (agents2.includes(agent)) skillEligibleAgents.add(agent);
|
|
4991
5547
|
reverifications.push({
|
|
4992
5548
|
agent,
|
|
4993
5549
|
connectionId: connection.id,
|
|
@@ -4995,7 +5551,7 @@ async function connectEnterpriseMcp(input2) {
|
|
|
4995
5551
|
});
|
|
4996
5552
|
}
|
|
4997
5553
|
}
|
|
4998
|
-
for (const agent of
|
|
5554
|
+
for (const agent of agents2) {
|
|
4999
5555
|
if (skillEligibleAgents.has(agent)) {
|
|
5000
5556
|
stages.push(await installSkillForAgent(input2, agent));
|
|
5001
5557
|
} else if (zeroToolSkillAgents.has(agent)) {
|
|
@@ -5294,7 +5850,7 @@ async function inspectEnterpriseMcpStatus(input2) {
|
|
|
5294
5850
|
} catch {
|
|
5295
5851
|
health = "unavailable";
|
|
5296
5852
|
}
|
|
5297
|
-
const
|
|
5853
|
+
const agents2 = await Promise.all(
|
|
5298
5854
|
supportedAgents.map(async (agent) => {
|
|
5299
5855
|
const [mcpInspection, skillInspection] = await Promise.allSettled([
|
|
5300
5856
|
inspectEnterpriseAgent(input2.runtime.agents, {
|
|
@@ -5322,7 +5878,7 @@ async function inspectEnterpriseMcpStatus(input2) {
|
|
|
5322
5878
|
return { agent, mcp, skill, nextActions: nextActions2 };
|
|
5323
5879
|
})
|
|
5324
5880
|
);
|
|
5325
|
-
const nextActions =
|
|
5881
|
+
const nextActions = agents2.flatMap((agent) => agent.nextActions);
|
|
5326
5882
|
if (health !== "ready") {
|
|
5327
5883
|
nextActions.unshift(`\u68C0\u67E5\u4F01\u4E1A MCP ${connection.id} \u7684\u7F51\u7EDC\u548C\u51ED\u636E`);
|
|
5328
5884
|
}
|
|
@@ -5336,7 +5892,7 @@ async function inspectEnterpriseMcpStatus(input2) {
|
|
|
5336
5892
|
health,
|
|
5337
5893
|
toolCount,
|
|
5338
5894
|
risk,
|
|
5339
|
-
agents,
|
|
5895
|
+
agents: agents2,
|
|
5340
5896
|
nextActions
|
|
5341
5897
|
};
|
|
5342
5898
|
}
|
|
@@ -5694,9 +6250,9 @@ async function sourceStatus(client, sourceId) {
|
|
|
5694
6250
|
}
|
|
5695
6251
|
|
|
5696
6252
|
// src/core/context.ts
|
|
5697
|
-
import { createHash as
|
|
6253
|
+
import { createHash as createHash5 } from "crypto";
|
|
5698
6254
|
function directCredentialRef(url) {
|
|
5699
|
-
const fingerprint =
|
|
6255
|
+
const fingerprint = createHash5("sha256").update(url).digest("hex").slice(0, 16);
|
|
5700
6256
|
return `sag-cli/direct-${fingerprint}`;
|
|
5701
6257
|
}
|
|
5702
6258
|
async function createRuntimeContext(input2) {
|
|
@@ -5943,12 +6499,14 @@ async function runDoctor(client, input2, localMcpChecks) {
|
|
|
5943
6499
|
// src/output/redaction.ts
|
|
5944
6500
|
var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
5945
6501
|
"authorization",
|
|
6502
|
+
"authorization_code",
|
|
5946
6503
|
"token",
|
|
5947
6504
|
"access_token",
|
|
5948
6505
|
"password",
|
|
5949
6506
|
"secret",
|
|
5950
6507
|
"secretkey",
|
|
5951
|
-
"secret_key"
|
|
6508
|
+
"secret_key",
|
|
6509
|
+
"ticket"
|
|
5952
6510
|
]);
|
|
5953
6511
|
var BEARER_TOKEN = /\bBearer\s+[^\s"',}\]]+/giu;
|
|
5954
6512
|
function redactString(value) {
|
|
@@ -6095,7 +6653,7 @@ function renderEnterpriseMcpStages(value) {
|
|
|
6095
6653
|
function renderEnterpriseMcpStatus(value) {
|
|
6096
6654
|
const connection = isRecord(value.connection) ? value.connection : {};
|
|
6097
6655
|
const risk = isRecord(value.risk) ? value.risk : {};
|
|
6098
|
-
const
|
|
6656
|
+
const agents2 = Array.isArray(value.agents) ? value.agents.filter(isRecord) : [];
|
|
6099
6657
|
const nextActions = Array.isArray(value.nextActions) ? value.nextActions.map(String) : [];
|
|
6100
6658
|
const lines = [
|
|
6101
6659
|
`\u8FDE\u63A5\uFF1A${String(connection.id ?? "unknown")}${connection.active ? "\uFF08\u5F53\u524D\uFF09" : ""}`,
|
|
@@ -6103,7 +6661,7 @@ function renderEnterpriseMcpStatus(value) {
|
|
|
6103
6661
|
`\u5065\u5EB7\uFF1A${String(value.health ?? "unknown")}`,
|
|
6104
6662
|
`\u5DE5\u5177\uFF1A${String(value.toolCount ?? 0)}`,
|
|
6105
6663
|
`\u98CE\u9669\uFF1A\u53EA\u8BFB ${String(risk.readOnly ?? 0)}\uFF0C\u5199\u5165 ${String(risk.write ?? 0)}\uFF0C\u5220\u9664 ${String(risk.destructive ?? 0)}\uFF0C\u672A\u77E5 ${String(risk.unknown ?? 0)}`,
|
|
6106
|
-
...
|
|
6664
|
+
...agents2.map(
|
|
6107
6665
|
(agent) => `Agent ${String(agent.agent ?? "unknown")}\uFF1AMCP ${String(agent.mcp ?? "unknown")}\uFF0CSkill ${String(agent.skill ?? "unknown")}`
|
|
6108
6666
|
),
|
|
6109
6667
|
...nextActions.length ? ["\u4E0B\u4E00\u6B65\uFF1A", ...nextActions.map((action) => ` ${action}`)] : []
|
|
@@ -6208,190 +6766,1836 @@ function renderJson(result) {
|
|
|
6208
6766
|
`;
|
|
6209
6767
|
}
|
|
6210
6768
|
|
|
6211
|
-
// src/
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6769
|
+
// src/output/ndjson.ts
|
|
6770
|
+
var terminalEvents = /* @__PURE__ */ new Set([
|
|
6771
|
+
"completed",
|
|
6772
|
+
"failed",
|
|
6773
|
+
"detached",
|
|
6774
|
+
"completed_with_errors"
|
|
6775
|
+
]);
|
|
6776
|
+
var SkillEventWriter = class {
|
|
6777
|
+
constructor(write) {
|
|
6778
|
+
this.write = write;
|
|
6779
|
+
}
|
|
6780
|
+
write;
|
|
6781
|
+
terminal = false;
|
|
6782
|
+
emit(event) {
|
|
6783
|
+
if (this.terminal) throw new Error("Cannot emit after terminal event");
|
|
6784
|
+
if (terminalEvents.has(event.event)) {
|
|
6785
|
+
throw new Error("terminal event must be sent with complete");
|
|
6786
|
+
}
|
|
6787
|
+
this.write(`${JSON.stringify(redact(event))}
|
|
6788
|
+
`);
|
|
6221
6789
|
}
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6790
|
+
complete(event) {
|
|
6791
|
+
if (!terminalEvents.has(event.event)) {
|
|
6792
|
+
throw new Error("A terminal event is required");
|
|
6793
|
+
}
|
|
6794
|
+
if (this.terminal) throw new Error("Cannot emit a second terminal event");
|
|
6795
|
+
this.terminal = true;
|
|
6796
|
+
this.write(`${JSON.stringify(redact(event))}
|
|
6797
|
+
`);
|
|
6227
6798
|
}
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6799
|
+
};
|
|
6800
|
+
|
|
6801
|
+
// src/skill/installer.ts
|
|
6802
|
+
import { createHash as createHash6, randomUUID as randomUUID6 } from "crypto";
|
|
6803
|
+
import {
|
|
6804
|
+
cp as cp2,
|
|
6805
|
+
lstat as lstat3,
|
|
6806
|
+
mkdir as mkdir10,
|
|
6807
|
+
readFile as readFile12,
|
|
6808
|
+
readdir as readdir2,
|
|
6809
|
+
rename as rename11,
|
|
6810
|
+
rm as rm2,
|
|
6811
|
+
writeFile as writeFile6
|
|
6812
|
+
} from "fs/promises";
|
|
6813
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6814
|
+
import path13 from "path";
|
|
6815
|
+
import { lock as lock8 } from "proper-lockfile";
|
|
6816
|
+
var skillName2 = "sag-knowledge";
|
|
6817
|
+
var packageName = "@zleap-ai/sag-cli";
|
|
6818
|
+
var agents = ["codex", "claude-code", "workbuddy"];
|
|
6819
|
+
var requiredFiles = [
|
|
6820
|
+
"SKILL.md",
|
|
6821
|
+
"references/cli-reference.md",
|
|
6822
|
+
"references/ndjson-contract.md",
|
|
6823
|
+
"references/safety.md",
|
|
6824
|
+
"scripts/sag-knowledge.cjs"
|
|
6825
|
+
];
|
|
6826
|
+
function bundledSkillPath2() {
|
|
6827
|
+
const directory = path13.dirname(fileURLToPath2(import.meta.url));
|
|
6828
|
+
const packageRoot = path13.basename(directory) === "dist" ? path13.dirname(directory) : path13.resolve(directory, "../..");
|
|
6829
|
+
return path13.join(packageRoot, "skill");
|
|
6232
6830
|
}
|
|
6233
|
-
function
|
|
6234
|
-
|
|
6235
|
-
|
|
6831
|
+
function uniqueAgents(selected) {
|
|
6832
|
+
return [...new Set(selected ?? agents)];
|
|
6833
|
+
}
|
|
6834
|
+
function skillTargetPath(agent, input2) {
|
|
6835
|
+
const home = path13.resolve(input2.home);
|
|
6836
|
+
const codexHome = input2.environment.CODEX_HOME ? path13.resolve(input2.environment.CODEX_HOME) : path13.join(home, ".codex");
|
|
6837
|
+
switch (agent) {
|
|
6838
|
+
case "codex":
|
|
6839
|
+
return path13.join(codexHome, "skills", skillName2);
|
|
6840
|
+
case "claude-code":
|
|
6841
|
+
return path13.join(home, ".claude", "skills", skillName2);
|
|
6842
|
+
case "workbuddy":
|
|
6843
|
+
return path13.join(home, ".workbuddy", "skills", skillName2);
|
|
6236
6844
|
}
|
|
6237
|
-
throw new CliError("INVALID_ARGUMENT", `\u4E0D\u652F\u6301\u7684 Agent\uFF1A${value}`, {
|
|
6238
|
-
exitCode: exitCodes.invalidArgument,
|
|
6239
|
-
hint: "\u8BF7\u9009\u62E9 codex\u3001claude-code \u6216 workbuddy\u3002"
|
|
6240
|
-
});
|
|
6241
6845
|
}
|
|
6242
|
-
function
|
|
6243
|
-
|
|
6244
|
-
throw new CliError("INVALID_ARGUMENT", `\u4E0D\u652F\u6301\u7684\u51ED\u636E\u5B58\u50A8\uFF1A${value}`, {
|
|
6245
|
-
exitCode: exitCodes.invalidArgument,
|
|
6246
|
-
hint: "\u8BF7\u9009\u62E9 keychain \u6216 file\u3002"
|
|
6247
|
-
});
|
|
6846
|
+
function sourceTargetError() {
|
|
6847
|
+
throw new Error("Skill target is outside the configured user-level Agent directory");
|
|
6248
6848
|
}
|
|
6249
|
-
function
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
throw new CliError("INVALID_ARGUMENT", "\u8BF7\u4F7F\u7528 --agent \u6307\u5B9A Agent", {
|
|
6260
|
-
exitCode: exitCodes.invalidArgument,
|
|
6261
|
-
hint: "\u8BF7\u9009\u62E9 codex\u3001claude-code \u6216 workbuddy\u3002"
|
|
6262
|
-
});
|
|
6263
|
-
}
|
|
6264
|
-
return agent;
|
|
6849
|
+
function assertUserTarget(target, agent, runtime2) {
|
|
6850
|
+
const parent = agent === "codex" ? path13.resolve(
|
|
6851
|
+
runtime2.environment.CODEX_HOME ?? path13.join(runtime2.home, ".codex"),
|
|
6852
|
+
"skills"
|
|
6853
|
+
) : path13.resolve(
|
|
6854
|
+
runtime2.home,
|
|
6855
|
+
agent === "claude-code" ? ".claude/skills" : ".workbuddy/skills"
|
|
6856
|
+
);
|
|
6857
|
+
if (path13.dirname(target) !== parent || path13.basename(target) !== skillName2)
|
|
6858
|
+
sourceTargetError();
|
|
6265
6859
|
}
|
|
6266
|
-
|
|
6860
|
+
var SkillTargetLeaseError = class extends Error {
|
|
6861
|
+
};
|
|
6862
|
+
async function withTargetLease(target, operation) {
|
|
6863
|
+
await mkdir10(path13.dirname(target), { recursive: true });
|
|
6864
|
+
let release;
|
|
6267
6865
|
try {
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
} catch (cause) {
|
|
6274
|
-
throw new CliError("INVALID_ARGUMENT", "--arguments-json \u5FC5\u987B\u662F\u4E00\u4E2A JSON \u5BF9\u8C61", {
|
|
6275
|
-
exitCode: exitCodes.invalidArgument,
|
|
6276
|
-
cause
|
|
6866
|
+
release = await lock8(path13.join(path13.dirname(target), `.${skillName2}.lease`), {
|
|
6867
|
+
realpath: false,
|
|
6868
|
+
stale: 1e4,
|
|
6869
|
+
update: 2e3,
|
|
6870
|
+
retries: { retries: 100, factor: 1, minTimeout: 10, maxTimeout: 10 }
|
|
6277
6871
|
});
|
|
6872
|
+
} catch {
|
|
6873
|
+
throw new SkillTargetLeaseError();
|
|
6874
|
+
}
|
|
6875
|
+
try {
|
|
6876
|
+
return await operation();
|
|
6877
|
+
} finally {
|
|
6878
|
+
await release();
|
|
6278
6879
|
}
|
|
6279
6880
|
}
|
|
6280
|
-
function
|
|
6281
|
-
|
|
6881
|
+
async function exists2(location) {
|
|
6882
|
+
try {
|
|
6883
|
+
await lstat3(location);
|
|
6884
|
+
return true;
|
|
6885
|
+
} catch (error) {
|
|
6886
|
+
if (error.code === "ENOENT") return false;
|
|
6887
|
+
throw error;
|
|
6888
|
+
}
|
|
6282
6889
|
}
|
|
6283
|
-
function
|
|
6284
|
-
return
|
|
6890
|
+
async function moveTarget(runtime2, from, to) {
|
|
6891
|
+
if (runtime2.filesystem?.rename) return runtime2.filesystem.rename(from, to);
|
|
6892
|
+
await rename11(from, to);
|
|
6285
6893
|
}
|
|
6286
|
-
function
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
options.json ? renderJson(success3(data)) : renderHuman(data, options.quiet ?? false)
|
|
6290
|
-
);
|
|
6894
|
+
async function removeTarget(runtime2, target) {
|
|
6895
|
+
if (runtime2.filesystem?.remove) return runtime2.filesystem.remove(target);
|
|
6896
|
+
await rm2(target, { recursive: true, force: true });
|
|
6291
6897
|
}
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
return createRuntimeContext({
|
|
6295
|
-
options: {
|
|
6296
|
-
...options.url ? { url: options.url } : {},
|
|
6297
|
-
...options.profile ? { profile: options.profile } : {}
|
|
6298
|
-
},
|
|
6299
|
-
environment: dependencies2.environment,
|
|
6300
|
-
configStore: dependencies2.configStore,
|
|
6301
|
-
credentialStore: dependencies2.credentialStore,
|
|
6302
|
-
createClient: dependencies2.createClient
|
|
6303
|
-
});
|
|
6898
|
+
function sameManagedSkill(left, right) {
|
|
6899
|
+
return left?.agent === right.agent && left.target === right.target && left.fingerprint === right.fingerprint && left.version === right.version && left.backupPath === right.backupPath;
|
|
6304
6900
|
}
|
|
6305
|
-
function
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6901
|
+
async function deactivateUnmanagedTarget(input2) {
|
|
6902
|
+
try {
|
|
6903
|
+
await moveTarget(input2.runtime, input2.target, input2.removed);
|
|
6904
|
+
} catch {
|
|
6905
|
+
return "active-unmanaged";
|
|
6310
6906
|
}
|
|
6311
|
-
return
|
|
6907
|
+
return await exists2(input2.target) ? "active-unmanaged" : "incomplete";
|
|
6312
6908
|
}
|
|
6313
|
-
function
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6909
|
+
async function recoverUninstall(input2) {
|
|
6910
|
+
try {
|
|
6911
|
+
await moveTarget(input2.runtime, input2.removed, input2.target);
|
|
6912
|
+
} catch {
|
|
6913
|
+
return "incomplete";
|
|
6318
6914
|
}
|
|
6319
|
-
return
|
|
6915
|
+
if (!await exists2(input2.target)) return "incomplete";
|
|
6916
|
+
try {
|
|
6917
|
+
await validateBundle(input2.target, input2.agent);
|
|
6918
|
+
if (await fingerprintDirectory(input2.target) !== input2.managed.fingerprint) {
|
|
6919
|
+
return deactivateUnmanagedTarget(input2);
|
|
6920
|
+
}
|
|
6921
|
+
} catch {
|
|
6922
|
+
return deactivateUnmanagedTarget(input2);
|
|
6923
|
+
}
|
|
6924
|
+
try {
|
|
6925
|
+
const current = await input2.runtime.state.getManagedSkill(
|
|
6926
|
+
input2.agent,
|
|
6927
|
+
input2.target
|
|
6928
|
+
);
|
|
6929
|
+
if (sameManagedSkill(current, input2.managed)) return "restored";
|
|
6930
|
+
await input2.runtime.state.saveManagedSkill(input2.managed);
|
|
6931
|
+
const restored = await input2.runtime.state.getManagedSkill(
|
|
6932
|
+
input2.agent,
|
|
6933
|
+
input2.target
|
|
6934
|
+
);
|
|
6935
|
+
if (sameManagedSkill(restored, input2.managed)) return "restored";
|
|
6936
|
+
} catch {
|
|
6937
|
+
}
|
|
6938
|
+
return deactivateUnmanagedTarget(input2);
|
|
6320
6939
|
}
|
|
6321
|
-
function
|
|
6322
|
-
return {
|
|
6323
|
-
...enterpriseMcpRuntime(dependencies2),
|
|
6324
|
-
cliVersion: dependencies2.version,
|
|
6325
|
-
timeoutMs
|
|
6326
|
-
};
|
|
6940
|
+
function digest(value) {
|
|
6941
|
+
return `sha256:${createHash6("sha256").update(value).digest("hex")}`;
|
|
6327
6942
|
}
|
|
6328
|
-
function
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
})
|
|
6943
|
+
function parseManifest(raw) {
|
|
6944
|
+
const value = JSON.parse(raw);
|
|
6945
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6946
|
+
throw new Error("invalid manifest");
|
|
6947
|
+
const manifest = value;
|
|
6948
|
+
if (manifest.schema !== 1 || manifest.name !== skillName2 || manifest.package !== packageName || typeof manifest.packageVersion !== "string" || !manifest.packageVersion || typeof manifest.bundleDigest !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(manifest.bundleDigest) || !agents.includes(manifest.agent) && manifest.agent !== "template") {
|
|
6949
|
+
throw new Error("invalid manifest");
|
|
6950
|
+
}
|
|
6951
|
+
return manifest;
|
|
6334
6952
|
}
|
|
6335
|
-
async function
|
|
6336
|
-
const
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6953
|
+
async function validateBundle(bundle, expectedAgent) {
|
|
6954
|
+
const [rawManifest, runtime2] = await Promise.all([
|
|
6955
|
+
readFile12(path13.join(bundle, "manifest.json"), "utf8"),
|
|
6956
|
+
readFile12(path13.join(bundle, "scripts", "sag-knowledge.cjs"))
|
|
6957
|
+
]);
|
|
6958
|
+
const manifest = parseManifest(rawManifest);
|
|
6959
|
+
if (manifest.agent !== expectedAgent || manifest.bundleDigest !== digest(runtime2)) {
|
|
6960
|
+
throw new Error("invalid Skill bundle");
|
|
6961
|
+
}
|
|
6962
|
+
await Promise.all(requiredFiles.map((file) => readFile12(path13.join(bundle, file))));
|
|
6963
|
+
return manifest;
|
|
6964
|
+
}
|
|
6965
|
+
async function fingerprintDirectory(root, relative = "") {
|
|
6966
|
+
const hash = createHash6("sha256");
|
|
6967
|
+
async function visit(nested = "") {
|
|
6968
|
+
const entries = await readdir2(path13.join(root, nested), { withFileTypes: true });
|
|
6969
|
+
for (const entry of entries.sort(
|
|
6970
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
6971
|
+
)) {
|
|
6972
|
+
const entryPath = path13.join(nested, entry.name);
|
|
6973
|
+
if (entry.isDirectory()) {
|
|
6974
|
+
hash.update(`directory:${entryPath}
|
|
6975
|
+
`);
|
|
6976
|
+
await visit(entryPath);
|
|
6977
|
+
} else if (entry.isFile()) {
|
|
6978
|
+
hash.update(`file:${entryPath}
|
|
6979
|
+
`);
|
|
6980
|
+
hash.update(await readFile12(path13.join(root, entryPath)));
|
|
6981
|
+
} else {
|
|
6982
|
+
throw new Error("unsupported Skill file");
|
|
6983
|
+
}
|
|
6984
|
+
}
|
|
6340
6985
|
}
|
|
6341
|
-
|
|
6986
|
+
await visit(relative);
|
|
6987
|
+
return `sha256:${hash.digest("hex")}`;
|
|
6342
6988
|
}
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6989
|
+
function sibling(target, suffix) {
|
|
6990
|
+
return path13.join(path13.dirname(target), `.${skillName2}.${suffix}-${randomUUID6()}`);
|
|
6991
|
+
}
|
|
6992
|
+
async function restore(input2) {
|
|
6993
|
+
let recovered = true;
|
|
6994
|
+
try {
|
|
6995
|
+
await removeTarget(input2.runtime, input2.temporary);
|
|
6996
|
+
} catch {
|
|
6997
|
+
recovered = false;
|
|
6998
|
+
}
|
|
6999
|
+
const targetMatchesReplacement = async () => {
|
|
7000
|
+
if (!input2.replacementFingerprint || !await exists2(input2.target)) return false;
|
|
7001
|
+
try {
|
|
7002
|
+
return await fingerprintDirectory(input2.target) === input2.replacementFingerprint;
|
|
7003
|
+
} catch {
|
|
7004
|
+
return false;
|
|
7005
|
+
}
|
|
6348
7006
|
};
|
|
7007
|
+
if (input2.backup && await exists2(input2.backup)) {
|
|
7008
|
+
if (await exists2(input2.target)) {
|
|
7009
|
+
if (!await targetMatchesReplacement()) return false;
|
|
7010
|
+
try {
|
|
7011
|
+
await removeTarget(input2.runtime, input2.target);
|
|
7012
|
+
} catch {
|
|
7013
|
+
return false;
|
|
7014
|
+
}
|
|
7015
|
+
}
|
|
7016
|
+
try {
|
|
7017
|
+
await moveTarget(input2.runtime, input2.backup, input2.target);
|
|
7018
|
+
} catch {
|
|
7019
|
+
return false;
|
|
7020
|
+
}
|
|
7021
|
+
} else if (!input2.hadTarget && await exists2(input2.target)) {
|
|
7022
|
+
if (!await targetMatchesReplacement()) return false;
|
|
7023
|
+
try {
|
|
7024
|
+
await removeTarget(input2.runtime, input2.target);
|
|
7025
|
+
} catch {
|
|
7026
|
+
return false;
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
return recovered;
|
|
6349
7030
|
}
|
|
6350
|
-
function
|
|
6351
|
-
const
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
7031
|
+
async function inspectOne(runtime2, agent, inspectExplicitTarget = false) {
|
|
7032
|
+
const target = skillTargetPath(agent, runtime2);
|
|
7033
|
+
assertUserTarget(target, agent, runtime2);
|
|
7034
|
+
if (!inspectExplicitTarget && !await runtime2.agents[agent].installed()) {
|
|
7035
|
+
return { agent, target, status: "unavailable", message: "Agent is not installed." };
|
|
7036
|
+
}
|
|
7037
|
+
const managed = await runtime2.state.getManagedSkill(agent, target);
|
|
7038
|
+
if (!await exists2(target)) {
|
|
7039
|
+
return managed ? {
|
|
7040
|
+
agent,
|
|
7041
|
+
target,
|
|
7042
|
+
status: "drifted",
|
|
7043
|
+
code: "INSTALL_TARGET_CONFLICT",
|
|
7044
|
+
message: "Managed Skill directory is missing."
|
|
7045
|
+
} : { agent, target, status: "missing", message: "Skill is not installed." };
|
|
7046
|
+
}
|
|
7047
|
+
if (!managed) {
|
|
7048
|
+
return {
|
|
7049
|
+
agent,
|
|
7050
|
+
target,
|
|
7051
|
+
status: "unmanaged",
|
|
7052
|
+
code: "INSTALL_TARGET_CONFLICT",
|
|
7053
|
+
message: "Existing Skill directory is not managed by SAG CLI."
|
|
7054
|
+
};
|
|
7055
|
+
}
|
|
7056
|
+
try {
|
|
7057
|
+
const manifest = await validateBundle(target, agent);
|
|
7058
|
+
const fingerprint = await fingerprintDirectory(target);
|
|
7059
|
+
if (managed.fingerprint !== fingerprint || managed.version !== manifest.packageVersion) {
|
|
7060
|
+
return {
|
|
7061
|
+
agent,
|
|
7062
|
+
target,
|
|
7063
|
+
status: "drifted",
|
|
7064
|
+
code: "INSTALL_TARGET_CONFLICT",
|
|
7065
|
+
message: "Managed Skill directory has changed outside SAG CLI."
|
|
7066
|
+
};
|
|
6368
7067
|
}
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
7068
|
+
return {
|
|
7069
|
+
agent,
|
|
7070
|
+
target,
|
|
7071
|
+
status: "installed",
|
|
7072
|
+
version: manifest.packageVersion,
|
|
7073
|
+
package: manifest.package,
|
|
7074
|
+
message: "Managed Skill is installed."
|
|
7075
|
+
};
|
|
7076
|
+
} catch {
|
|
7077
|
+
return {
|
|
7078
|
+
agent,
|
|
7079
|
+
target,
|
|
7080
|
+
status: "drifted",
|
|
7081
|
+
code: "INSTALL_TARGET_CONFLICT",
|
|
7082
|
+
message: "Managed Skill directory has changed outside SAG CLI."
|
|
7083
|
+
};
|
|
7084
|
+
}
|
|
6372
7085
|
}
|
|
6373
|
-
function
|
|
6374
|
-
const
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
7086
|
+
async function activeAgents(runtime2, selected) {
|
|
7087
|
+
const choices = uniqueAgents(selected);
|
|
7088
|
+
if (selected) return choices;
|
|
7089
|
+
const installed = await Promise.all(
|
|
7090
|
+
choices.map(async (agent) => ({
|
|
7091
|
+
agent,
|
|
7092
|
+
installed: await runtime2.agents[agent].installed()
|
|
7093
|
+
}))
|
|
7094
|
+
);
|
|
7095
|
+
return installed.filter((choice) => choice.installed).map((choice) => choice.agent);
|
|
7096
|
+
}
|
|
7097
|
+
async function requireConfirmation(runtime2, selection, action) {
|
|
7098
|
+
if (selection.yes) return true;
|
|
7099
|
+
return await runtime2.confirm?.(`${action} SAG Knowledge Skill?`) ?? false;
|
|
7100
|
+
}
|
|
7101
|
+
async function inspectSkillTargets(runtime2, input2) {
|
|
7102
|
+
const source = await validateBundle(
|
|
7103
|
+
runtime2.bundlePath ?? bundledSkillPath2(),
|
|
7104
|
+
"template"
|
|
7105
|
+
);
|
|
7106
|
+
const selected = input2.agents ? uniqueAgents(input2.agents) : agents;
|
|
7107
|
+
const targets = await Promise.all(
|
|
7108
|
+
selected.map((agent) => inspectOne(runtime2, agent, input2.agents !== void 0))
|
|
7109
|
+
);
|
|
7110
|
+
return {
|
|
7111
|
+
package: source.package,
|
|
7112
|
+
version: source.packageVersion,
|
|
7113
|
+
targets,
|
|
7114
|
+
completedWithErrors: targets.some((target) => target.status === "drifted")
|
|
7115
|
+
};
|
|
7116
|
+
}
|
|
7117
|
+
async function installSkillTargets(runtime2, input2) {
|
|
7118
|
+
if (input2.update && !runtime2.resolveLatestBundle) {
|
|
7119
|
+
throw new CliError(
|
|
7120
|
+
"DEPENDENCY_MISSING",
|
|
7121
|
+
"Unable to resolve @latest Skill package",
|
|
7122
|
+
{
|
|
7123
|
+
exitCode: exitCodes.dependencyMissing,
|
|
7124
|
+
hint: "Run this update through `npx @zleap-ai/sag-cli@latest` or restore npm access."
|
|
7125
|
+
}
|
|
7126
|
+
);
|
|
7127
|
+
}
|
|
7128
|
+
const resolved = input2.update ? await runtime2.resolveLatestBundle?.() : void 0;
|
|
7129
|
+
const sourcePath = resolved?.bundlePath ?? runtime2.bundlePath ?? bundledSkillPath2();
|
|
7130
|
+
const source = await validateBundle(sourcePath, "template");
|
|
7131
|
+
const selected = await activeAgents(runtime2, input2.agents);
|
|
7132
|
+
if (selected.length === 0) {
|
|
7133
|
+
return {
|
|
7134
|
+
package: source.package,
|
|
7135
|
+
version: source.packageVersion,
|
|
7136
|
+
targets: agents.map((agent) => ({
|
|
7137
|
+
agent,
|
|
7138
|
+
target: skillTargetPath(agent, runtime2),
|
|
7139
|
+
status: "unavailable",
|
|
7140
|
+
message: "No supported Agent was detected on this device."
|
|
7141
|
+
})),
|
|
7142
|
+
completedWithErrors: true
|
|
7143
|
+
};
|
|
7144
|
+
}
|
|
7145
|
+
if (!await requireConfirmation(runtime2, input2, "Install")) {
|
|
7146
|
+
return {
|
|
7147
|
+
package: source.package,
|
|
7148
|
+
version: source.packageVersion,
|
|
7149
|
+
targets: selected.map((agent) => ({
|
|
7150
|
+
agent,
|
|
7151
|
+
target: skillTargetPath(agent, runtime2),
|
|
7152
|
+
status: "skipped",
|
|
7153
|
+
message: "Installation was not confirmed."
|
|
7154
|
+
})),
|
|
7155
|
+
completedWithErrors: false
|
|
7156
|
+
};
|
|
7157
|
+
}
|
|
7158
|
+
const targets = [];
|
|
7159
|
+
try {
|
|
7160
|
+
for (const agent of selected) {
|
|
7161
|
+
const target = skillTargetPath(agent, runtime2);
|
|
7162
|
+
assertUserTarget(target, agent, runtime2);
|
|
7163
|
+
try {
|
|
7164
|
+
targets.push(
|
|
7165
|
+
await withTargetLease(target, async () => {
|
|
7166
|
+
const inspection = await inspectOne(
|
|
7167
|
+
runtime2,
|
|
7168
|
+
agent,
|
|
7169
|
+
input2.agents !== void 0
|
|
7170
|
+
);
|
|
7171
|
+
if (inspection.status === "installed" && inspection.version === source.packageVersion) {
|
|
7172
|
+
return {
|
|
7173
|
+
...inspection,
|
|
7174
|
+
status: "skipped",
|
|
7175
|
+
message: "Managed Skill is already current."
|
|
7176
|
+
};
|
|
7177
|
+
}
|
|
7178
|
+
if (inspection.status === "unmanaged" || inspection.status === "drifted") {
|
|
7179
|
+
return { ...inspection, status: "conflict" };
|
|
7180
|
+
}
|
|
7181
|
+
if (inspection.status === "unavailable") return inspection;
|
|
7182
|
+
const temporary = sibling(target, "tmp");
|
|
7183
|
+
const hadTarget = inspection.status === "installed";
|
|
7184
|
+
let backup;
|
|
7185
|
+
let replacementFingerprint;
|
|
7186
|
+
try {
|
|
7187
|
+
await cp2(sourcePath, temporary, {
|
|
7188
|
+
recursive: true,
|
|
7189
|
+
errorOnExist: true,
|
|
7190
|
+
force: false
|
|
7191
|
+
});
|
|
7192
|
+
const temporaryManifestPath = path13.join(temporary, "manifest.json");
|
|
7193
|
+
const temporaryManifest = await validateBundle(temporary, "template");
|
|
7194
|
+
await writeFile6(
|
|
7195
|
+
temporaryManifestPath,
|
|
7196
|
+
`${JSON.stringify({ ...temporaryManifest, agent }, null, 2)}
|
|
7197
|
+
`,
|
|
7198
|
+
"utf8"
|
|
7199
|
+
);
|
|
7200
|
+
await validateBundle(temporary, agent);
|
|
7201
|
+
replacementFingerprint = await fingerprintDirectory(temporary);
|
|
7202
|
+
await runtime2.agents[agent].installSkill?.();
|
|
7203
|
+
if (hadTarget) {
|
|
7204
|
+
backup = sibling(target, "backup");
|
|
7205
|
+
await moveTarget(runtime2, target, backup);
|
|
7206
|
+
}
|
|
7207
|
+
await moveTarget(runtime2, temporary, target);
|
|
7208
|
+
if (await fingerprintDirectory(target) !== replacementFingerprint) {
|
|
7209
|
+
throw new Error("Skill target changed during installation");
|
|
7210
|
+
}
|
|
7211
|
+
const managed = {
|
|
7212
|
+
agent,
|
|
7213
|
+
target,
|
|
7214
|
+
version: source.packageVersion,
|
|
7215
|
+
fingerprint: replacementFingerprint,
|
|
7216
|
+
...backup ? { backupPath: backup } : {}
|
|
7217
|
+
};
|
|
7218
|
+
await runtime2.state.saveManagedSkill(managed);
|
|
7219
|
+
return {
|
|
7220
|
+
agent,
|
|
7221
|
+
target,
|
|
7222
|
+
status: hadTarget ? "updated" : "created",
|
|
7223
|
+
version: source.packageVersion,
|
|
7224
|
+
package: source.package,
|
|
7225
|
+
message: "Skill installed."
|
|
7226
|
+
};
|
|
7227
|
+
} catch {
|
|
7228
|
+
const restored = await restore({
|
|
7229
|
+
runtime: runtime2,
|
|
7230
|
+
target,
|
|
7231
|
+
temporary,
|
|
7232
|
+
...backup ? { backup } : {},
|
|
7233
|
+
hadTarget,
|
|
7234
|
+
...replacementFingerprint ? { replacementFingerprint } : {}
|
|
7235
|
+
}).catch(() => false);
|
|
7236
|
+
return {
|
|
7237
|
+
agent,
|
|
7238
|
+
target,
|
|
7239
|
+
status: "failed",
|
|
7240
|
+
message: restored ? "Skill installation failed and this target was restored." : "Skill installation failed and recovery was incomplete."
|
|
7241
|
+
};
|
|
7242
|
+
}
|
|
7243
|
+
})
|
|
7244
|
+
);
|
|
7245
|
+
} catch (error) {
|
|
7246
|
+
if (!(error instanceof SkillTargetLeaseError)) throw error;
|
|
7247
|
+
targets.push({
|
|
7248
|
+
agent,
|
|
7249
|
+
target,
|
|
7250
|
+
status: "conflict",
|
|
7251
|
+
code: "INSTALL_TARGET_CONFLICT",
|
|
7252
|
+
message: "Skill target is busy; wait for the active SAG CLI operation and retry."
|
|
7253
|
+
});
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
return {
|
|
7257
|
+
package: source.package,
|
|
7258
|
+
version: source.packageVersion,
|
|
7259
|
+
targets,
|
|
7260
|
+
completedWithErrors: targets.some(
|
|
7261
|
+
(target) => ["failed", "conflict"].includes(target.status)
|
|
7262
|
+
)
|
|
7263
|
+
};
|
|
7264
|
+
} finally {
|
|
7265
|
+
await resolved?.cleanup();
|
|
7266
|
+
}
|
|
7267
|
+
}
|
|
7268
|
+
async function uninstallSkillTargets(runtime2, input2) {
|
|
7269
|
+
const source = await validateBundle(
|
|
7270
|
+
runtime2.bundlePath ?? bundledSkillPath2(),
|
|
7271
|
+
"template"
|
|
7272
|
+
);
|
|
7273
|
+
const selected = await activeAgents(runtime2, input2.agents);
|
|
7274
|
+
if (!await requireConfirmation(runtime2, input2, "Uninstall")) {
|
|
7275
|
+
return {
|
|
7276
|
+
package: source.package,
|
|
7277
|
+
version: source.packageVersion,
|
|
7278
|
+
targets: selected.map((agent) => ({
|
|
7279
|
+
agent,
|
|
7280
|
+
target: skillTargetPath(agent, runtime2),
|
|
7281
|
+
status: "skipped",
|
|
7282
|
+
message: "Uninstall was not confirmed."
|
|
7283
|
+
})),
|
|
7284
|
+
completedWithErrors: false
|
|
7285
|
+
};
|
|
7286
|
+
}
|
|
7287
|
+
const targets = [];
|
|
7288
|
+
for (const agent of selected) {
|
|
7289
|
+
const target = skillTargetPath(agent, runtime2);
|
|
7290
|
+
assertUserTarget(target, agent, runtime2);
|
|
7291
|
+
try {
|
|
7292
|
+
targets.push(
|
|
7293
|
+
await withTargetLease(target, async () => {
|
|
7294
|
+
const inspection = await inspectOne(
|
|
7295
|
+
runtime2,
|
|
7296
|
+
agent,
|
|
7297
|
+
input2.agents !== void 0
|
|
7298
|
+
);
|
|
7299
|
+
if (inspection.status === "missing") return inspection;
|
|
7300
|
+
if (inspection.status !== "installed") {
|
|
7301
|
+
return {
|
|
7302
|
+
...inspection,
|
|
7303
|
+
status: "conflict",
|
|
7304
|
+
code: "INSTALL_TARGET_CONFLICT"
|
|
7305
|
+
};
|
|
7306
|
+
}
|
|
7307
|
+
const removed = sibling(inspection.target, "remove");
|
|
7308
|
+
const managed = await runtime2.state.getManagedSkill(agent, inspection.target);
|
|
7309
|
+
try {
|
|
7310
|
+
await moveTarget(runtime2, inspection.target, removed);
|
|
7311
|
+
await runtime2.state.deleteManagedSkill(agent, inspection.target);
|
|
7312
|
+
await removeTarget(runtime2, removed);
|
|
7313
|
+
return {
|
|
7314
|
+
agent,
|
|
7315
|
+
target: inspection.target,
|
|
7316
|
+
status: "removed",
|
|
7317
|
+
...inspection.version ? { version: inspection.version } : {},
|
|
7318
|
+
package: source.package,
|
|
7319
|
+
message: "Managed Skill was removed."
|
|
7320
|
+
};
|
|
7321
|
+
} catch {
|
|
7322
|
+
const recovery = managed && await exists2(removed) ? await recoverUninstall({
|
|
7323
|
+
runtime: runtime2,
|
|
7324
|
+
agent,
|
|
7325
|
+
target: inspection.target,
|
|
7326
|
+
removed,
|
|
7327
|
+
managed
|
|
7328
|
+
}) : "incomplete";
|
|
7329
|
+
return {
|
|
7330
|
+
agent,
|
|
7331
|
+
target: inspection.target,
|
|
7332
|
+
status: "failed",
|
|
7333
|
+
message: recovery === "restored" ? "Skill uninstall failed; target and managed state were restored." : recovery === "active-unmanaged" ? "Skill uninstall failed; recovery is incomplete and the active target is unmanaged." : "Skill uninstall failed and recovery was incomplete."
|
|
7334
|
+
};
|
|
7335
|
+
}
|
|
7336
|
+
})
|
|
7337
|
+
);
|
|
7338
|
+
} catch (error) {
|
|
7339
|
+
if (!(error instanceof SkillTargetLeaseError)) throw error;
|
|
7340
|
+
targets.push({
|
|
7341
|
+
agent,
|
|
7342
|
+
target,
|
|
7343
|
+
status: "conflict",
|
|
7344
|
+
code: "INSTALL_TARGET_CONFLICT",
|
|
7345
|
+
message: "Skill target is busy; wait for the active SAG CLI operation and retry."
|
|
7346
|
+
});
|
|
7347
|
+
}
|
|
7348
|
+
}
|
|
7349
|
+
return {
|
|
7350
|
+
package: source.package,
|
|
7351
|
+
version: source.packageVersion,
|
|
7352
|
+
targets,
|
|
7353
|
+
completedWithErrors: targets.some(
|
|
7354
|
+
(target) => ["failed", "conflict"].includes(target.status)
|
|
7355
|
+
)
|
|
7356
|
+
};
|
|
7357
|
+
}
|
|
7358
|
+
var skillInstallerExitCode = exitCodes.configConflict;
|
|
7359
|
+
|
|
7360
|
+
// src/skill/account-auth.ts
|
|
7361
|
+
import { createHash as createHash7, randomBytes as nodeRandomBytes } from "crypto";
|
|
7362
|
+
import { setTimeout as delay } from "timers/promises";
|
|
7363
|
+
async function authorizeSkillAccount(runtime2, origin) {
|
|
7364
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
7365
|
+
const random = runtime2.randomBytes ?? nodeRandomBytes;
|
|
7366
|
+
const verifier2 = random(32).toString("base64url");
|
|
7367
|
+
const challenge = createHash7("sha256").update(verifier2).digest("base64url");
|
|
7368
|
+
const session = await runtime2.client.createLoginSession(normalizedOrigin, challenge);
|
|
7369
|
+
await runtime2.openBrowser(session.verificationUrl);
|
|
7370
|
+
const sleep = runtime2.sleep ?? delay;
|
|
7371
|
+
for (; ; ) {
|
|
7372
|
+
if (runtime2.now().getTime() >= new Date(session.expiresAt).getTime()) {
|
|
7373
|
+
throw expiredAuthorization();
|
|
7374
|
+
}
|
|
7375
|
+
const current = await runtime2.client.getLoginSession(
|
|
7376
|
+
normalizedOrigin,
|
|
7377
|
+
session.sessionId
|
|
7378
|
+
);
|
|
7379
|
+
if (current.status === "denied") {
|
|
7380
|
+
throw new SkillError(
|
|
7381
|
+
"PERMISSION_DENIED",
|
|
7382
|
+
"SAG account authorization was denied",
|
|
7383
|
+
{ exitCode: exitCodes.permissionDenied }
|
|
7384
|
+
);
|
|
7385
|
+
}
|
|
7386
|
+
if (current.status === "consumed") throw expiredAuthorization();
|
|
7387
|
+
if (current.status === "approved") {
|
|
7388
|
+
const account = await runtime2.client.exchange(normalizedOrigin, {
|
|
7389
|
+
sessionId: session.sessionId,
|
|
7390
|
+
pkceVerifier: verifier2
|
|
7391
|
+
});
|
|
7392
|
+
await runtime2.config.saveAccount(normalizedOrigin, account);
|
|
7393
|
+
return { origin: normalizedOrigin, account, browserOpened: true };
|
|
7394
|
+
}
|
|
7395
|
+
await sleep(1e3);
|
|
7396
|
+
}
|
|
7397
|
+
}
|
|
7398
|
+
async function withFreshSkillAccount(runtime2, origin, operation) {
|
|
7399
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
7400
|
+
const current = await requireAccount(runtime2, normalizedOrigin);
|
|
7401
|
+
if (isFuture(current.accessExpiresAt, runtime2.now())) {
|
|
7402
|
+
return operation(current.accessToken);
|
|
7403
|
+
}
|
|
7404
|
+
const accessToken = await runtime2.config.withRefreshLock(
|
|
7405
|
+
normalizedOrigin,
|
|
7406
|
+
async () => {
|
|
7407
|
+
const latest = await requireAccount(runtime2, normalizedOrigin);
|
|
7408
|
+
if (isFuture(latest.accessExpiresAt, runtime2.now())) return latest.accessToken;
|
|
7409
|
+
if (!isFuture(latest.refreshExpiresAt, runtime2.now())) {
|
|
7410
|
+
await runtime2.config.removeAccount(normalizedOrigin);
|
|
7411
|
+
throw expiredAuthorization();
|
|
7412
|
+
}
|
|
7413
|
+
try {
|
|
7414
|
+
const rotated = await runtime2.client.refresh(
|
|
7415
|
+
normalizedOrigin,
|
|
7416
|
+
latest.refreshToken
|
|
7417
|
+
);
|
|
7418
|
+
const next = {
|
|
7419
|
+
...rotated,
|
|
7420
|
+
...latest.recentKnowledgeBase && !rotated.recentKnowledgeBase ? { recentKnowledgeBase: latest.recentKnowledgeBase } : {}
|
|
7421
|
+
};
|
|
7422
|
+
await runtime2.config.saveAccount(normalizedOrigin, next);
|
|
7423
|
+
return next.accessToken;
|
|
7424
|
+
} catch (error) {
|
|
7425
|
+
if (error instanceof SkillError && (error.code === "AUTH_EXPIRED" || error.code === "AUTH_REQUIRED")) {
|
|
7426
|
+
await runtime2.config.removeAccount(normalizedOrigin);
|
|
7427
|
+
}
|
|
7428
|
+
throw error;
|
|
7429
|
+
}
|
|
7430
|
+
}
|
|
7431
|
+
);
|
|
7432
|
+
return operation(accessToken);
|
|
7433
|
+
}
|
|
7434
|
+
async function logoutSkillAccount(runtime2, origin) {
|
|
7435
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
7436
|
+
const account = await runtime2.config.getAccount(normalizedOrigin);
|
|
7437
|
+
if (!account) return false;
|
|
7438
|
+
await runtime2.client.logout(
|
|
7439
|
+
normalizedOrigin,
|
|
7440
|
+
account.accessToken,
|
|
7441
|
+
account.refreshToken
|
|
7442
|
+
);
|
|
7443
|
+
await runtime2.config.removeAccount(normalizedOrigin);
|
|
7444
|
+
return true;
|
|
7445
|
+
}
|
|
7446
|
+
async function requireAccount(runtime2, origin) {
|
|
7447
|
+
const account = await runtime2.config.getAccount(origin);
|
|
7448
|
+
if (account) return account;
|
|
7449
|
+
throw new SkillError("AUTH_REQUIRED", "SAG account authorization is required", {
|
|
7450
|
+
exitCode: exitCodes.authRequired
|
|
7451
|
+
});
|
|
7452
|
+
}
|
|
7453
|
+
function isFuture(value, now) {
|
|
7454
|
+
return new Date(value).getTime() > now.getTime();
|
|
7455
|
+
}
|
|
7456
|
+
function expiredAuthorization() {
|
|
7457
|
+
return new SkillError("AUTH_EXPIRED", "SAG account authorization has expired", {
|
|
7458
|
+
exitCode: exitCodes.authRequired
|
|
7459
|
+
});
|
|
7460
|
+
}
|
|
7461
|
+
|
|
7462
|
+
// src/skill/account-knowledge.ts
|
|
7463
|
+
async function renameSkillAccountDocument(runtime2, origin, input2) {
|
|
7464
|
+
const knowledgeBase = await selectedKnowledgeBase(
|
|
7465
|
+
runtime2,
|
|
7466
|
+
origin,
|
|
7467
|
+
input2.knowledgeBase
|
|
7468
|
+
);
|
|
7469
|
+
const renamed = await withSkillAccountKnowledgeClient(
|
|
7470
|
+
runtime2,
|
|
7471
|
+
origin,
|
|
7472
|
+
(client) => client.renameDocument({
|
|
7473
|
+
knowledgeBaseId: knowledgeBase.id,
|
|
7474
|
+
documentId: input2.documentId,
|
|
7475
|
+
expectedVersion: input2.expectedVersion,
|
|
7476
|
+
idempotencyKey: runtime2.createId(),
|
|
7477
|
+
title: input2.title
|
|
7478
|
+
})
|
|
7479
|
+
);
|
|
7480
|
+
await remember(runtime2, origin, knowledgeBase);
|
|
7481
|
+
return renamed;
|
|
7482
|
+
}
|
|
7483
|
+
async function deleteSkillAccountDocument(runtime2, origin, input2) {
|
|
7484
|
+
const knowledgeBase = await selectedKnowledgeBase(
|
|
7485
|
+
runtime2,
|
|
7486
|
+
origin,
|
|
7487
|
+
input2.knowledgeBase
|
|
7488
|
+
);
|
|
7489
|
+
const deleted = await withSkillAccountKnowledgeClient(
|
|
7490
|
+
runtime2,
|
|
7491
|
+
origin,
|
|
7492
|
+
(client) => client.deleteDocument({
|
|
7493
|
+
knowledgeBaseId: knowledgeBase.id,
|
|
7494
|
+
documentId: input2.documentId,
|
|
7495
|
+
expectedVersion: input2.expectedVersion,
|
|
7496
|
+
idempotencyKey: runtime2.createId()
|
|
7497
|
+
})
|
|
7498
|
+
);
|
|
7499
|
+
await remember(runtime2, origin, knowledgeBase);
|
|
7500
|
+
return deleted;
|
|
7501
|
+
}
|
|
7502
|
+
async function listSkillAccountKnowledgeBases(runtime2, origin) {
|
|
7503
|
+
return withSkillAccountKnowledgeClient(
|
|
7504
|
+
runtime2,
|
|
7505
|
+
origin,
|
|
7506
|
+
(client) => client.listKnowledgeBases()
|
|
7507
|
+
);
|
|
7508
|
+
}
|
|
7509
|
+
async function listSkillAccountDocuments(runtime2, origin, selector) {
|
|
7510
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
7511
|
+
const knowledgeBases = await listSkillAccountKnowledgeBases(
|
|
7512
|
+
runtime2,
|
|
7513
|
+
normalizedOrigin
|
|
7514
|
+
);
|
|
7515
|
+
const knowledgeBase = resolveKnowledgeBase(knowledgeBases, selector);
|
|
7516
|
+
const documents = await withSkillAccountKnowledgeClient(
|
|
7517
|
+
runtime2,
|
|
7518
|
+
normalizedOrigin,
|
|
7519
|
+
(client) => client.listDocuments(knowledgeBase.id)
|
|
7520
|
+
);
|
|
7521
|
+
await runtime2.config.saveRecentKnowledgeBase(normalizedOrigin, {
|
|
7522
|
+
id: knowledgeBase.id,
|
|
7523
|
+
name: knowledgeBase.name
|
|
7524
|
+
});
|
|
7525
|
+
return { knowledgeBase, documents };
|
|
7526
|
+
}
|
|
7527
|
+
async function askSkillAccountKnowledge(runtime2, origin, question, selector) {
|
|
7528
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
7529
|
+
let knowledgeBase;
|
|
7530
|
+
if (selector) {
|
|
7531
|
+
knowledgeBase = resolveKnowledgeBase(
|
|
7532
|
+
await listSkillAccountKnowledgeBases(runtime2, normalizedOrigin),
|
|
7533
|
+
selector
|
|
7534
|
+
);
|
|
7535
|
+
}
|
|
7536
|
+
const answer = await withSkillAccountKnowledgeClient(
|
|
7537
|
+
runtime2,
|
|
7538
|
+
normalizedOrigin,
|
|
7539
|
+
(client) => {
|
|
7540
|
+
if (!client.ask) {
|
|
7541
|
+
throw new SkillError(
|
|
7542
|
+
"PLATFORM_UNAVAILABLE",
|
|
7543
|
+
"SAG account question answering is unavailable",
|
|
7544
|
+
{ exitCode: exitCodes.serviceNotReady }
|
|
7545
|
+
);
|
|
7546
|
+
}
|
|
7547
|
+
return client.ask(question, knowledgeBase?.id);
|
|
7548
|
+
}
|
|
7549
|
+
);
|
|
7550
|
+
if (knowledgeBase) {
|
|
7551
|
+
await runtime2.config.saveRecentKnowledgeBase(normalizedOrigin, {
|
|
7552
|
+
id: knowledgeBase.id,
|
|
7553
|
+
name: knowledgeBase.name
|
|
7554
|
+
});
|
|
7555
|
+
}
|
|
7556
|
+
return answer;
|
|
7557
|
+
}
|
|
7558
|
+
function resolveKnowledgeBase(knowledgeBases, selector) {
|
|
7559
|
+
const exactId = knowledgeBases.find((item) => item.id === selector);
|
|
7560
|
+
if (exactId) return exactId;
|
|
7561
|
+
const exactAlias = knowledgeBases.filter((item) => item.alias === selector);
|
|
7562
|
+
if (exactAlias.length === 1) return exactAlias[0];
|
|
7563
|
+
const normalized = selector.trim().toLocaleLowerCase();
|
|
7564
|
+
const names = knowledgeBases.filter(
|
|
7565
|
+
(item) => item.name.toLocaleLowerCase() === normalized
|
|
7566
|
+
);
|
|
7567
|
+
if (names.length === 1) return names[0];
|
|
7568
|
+
throw new SkillError(
|
|
7569
|
+
"INVALID_ARGUMENT",
|
|
7570
|
+
names.length > 1 ? "Knowledge Base name is ambiguous" : "Knowledge Base was not found",
|
|
7571
|
+
{
|
|
7572
|
+
details: { selector },
|
|
7573
|
+
exitCode: exitCodes.invalidArgument
|
|
7574
|
+
}
|
|
7575
|
+
);
|
|
7576
|
+
}
|
|
7577
|
+
async function selectedKnowledgeBase(runtime2, origin, selector) {
|
|
7578
|
+
return resolveKnowledgeBase(
|
|
7579
|
+
await listSkillAccountKnowledgeBases(runtime2, origin),
|
|
7580
|
+
selector
|
|
7581
|
+
);
|
|
7582
|
+
}
|
|
7583
|
+
async function remember(runtime2, origin, knowledgeBase) {
|
|
7584
|
+
await runtime2.config.saveRecentKnowledgeBase(origin, {
|
|
7585
|
+
id: knowledgeBase.id,
|
|
7586
|
+
name: knowledgeBase.name
|
|
7587
|
+
});
|
|
7588
|
+
}
|
|
7589
|
+
async function withSkillAccountKnowledgeClient(runtime2, origin, operation) {
|
|
7590
|
+
const normalizedOrigin = normalizeSkillOrigin(origin);
|
|
7591
|
+
return withFreshSkillAccount(
|
|
7592
|
+
runtime2.auth,
|
|
7593
|
+
normalizedOrigin,
|
|
7594
|
+
(accessToken) => operation(runtime2.createClient({ accessToken, origin: normalizedOrigin }))
|
|
7595
|
+
);
|
|
7596
|
+
}
|
|
7597
|
+
|
|
7598
|
+
// src/skill/account-upload.ts
|
|
7599
|
+
import { createHash as createHash8 } from "crypto";
|
|
7600
|
+
import { createReadStream } from "fs";
|
|
7601
|
+
import { stat as stat3 } from "fs/promises";
|
|
7602
|
+
import path14 from "path";
|
|
7603
|
+
var DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
7604
|
+
async function uploadSkillAccountFile(runtime2, origin, filePath, knowledgeBaseSelector, waitMs = 30 * 60 * 1e3, overwriteDocumentId) {
|
|
7605
|
+
const file = await inspectFile(filePath);
|
|
7606
|
+
const knowledgeBase = resolveKnowledgeBase(
|
|
7607
|
+
await listSkillAccountKnowledgeBases(runtime2, origin),
|
|
7608
|
+
knowledgeBaseSelector
|
|
7609
|
+
);
|
|
7610
|
+
const serverLimit = await withSkillAccountKnowledgeClient(
|
|
7611
|
+
runtime2,
|
|
7612
|
+
origin,
|
|
7613
|
+
(client) => {
|
|
7614
|
+
if (!client.getServerLimit) return Promise.resolve(void 0);
|
|
7615
|
+
return client.getServerLimit(knowledgeBase.id);
|
|
7616
|
+
}
|
|
7617
|
+
);
|
|
7618
|
+
const limit = Math.min(
|
|
7619
|
+
DEFAULT_MAX_UPLOAD_BYTES,
|
|
7620
|
+
serverLimit ?? DEFAULT_MAX_UPLOAD_BYTES
|
|
7621
|
+
);
|
|
7622
|
+
if (file.size > limit) {
|
|
7623
|
+
throw new SkillError(
|
|
7624
|
+
"FILE_TOO_LARGE",
|
|
7625
|
+
`This file exceeds the ${limit}-byte Knowledge Base upload limit.`,
|
|
7626
|
+
{
|
|
7627
|
+
details: { limit_bytes: limit, size_bytes: file.size },
|
|
7628
|
+
exitCode: exitCodes.invalidArgument
|
|
7629
|
+
}
|
|
7630
|
+
);
|
|
7631
|
+
}
|
|
7632
|
+
const digest2 = await sha256(filePath);
|
|
7633
|
+
const receipt = await withSkillAccountKnowledgeClient(runtime2, origin, (client) => {
|
|
7634
|
+
if (!client.upload) return unavailableUpload();
|
|
7635
|
+
return client.upload({
|
|
7636
|
+
fileDigest: digest2,
|
|
7637
|
+
fileName: file.name,
|
|
7638
|
+
filePath,
|
|
7639
|
+
fileSize: file.size,
|
|
7640
|
+
idempotencyKey: runtime2.createId(),
|
|
7641
|
+
knowledgeBaseId: knowledgeBase.id,
|
|
7642
|
+
...overwriteDocumentId ? { overwriteDocumentId } : {}
|
|
7643
|
+
});
|
|
7644
|
+
});
|
|
7645
|
+
const accepted = {
|
|
7646
|
+
event: "accepted",
|
|
7647
|
+
importId: receipt.importId,
|
|
7648
|
+
documentId: receipt.documentId,
|
|
7649
|
+
jobId: receipt.jobId
|
|
7650
|
+
};
|
|
7651
|
+
await runtime2.onEvent?.(accepted);
|
|
7652
|
+
if (waitMs === 0) {
|
|
7653
|
+
const detached = { ...accepted, event: "detached" };
|
|
7654
|
+
await runtime2.onEvent?.(detached);
|
|
7655
|
+
return [accepted, detached];
|
|
7656
|
+
}
|
|
7657
|
+
const terminal = await poll(
|
|
7658
|
+
runtime2,
|
|
7659
|
+
origin,
|
|
7660
|
+
knowledgeBase.id,
|
|
7661
|
+
receipt.importId,
|
|
7662
|
+
waitMs
|
|
7663
|
+
);
|
|
7664
|
+
return [accepted, terminal];
|
|
7665
|
+
}
|
|
7666
|
+
async function inspectSkillAccountUpload(runtime2, origin, importId, knowledgeBaseSelector) {
|
|
7667
|
+
const knowledgeBase = resolveKnowledgeBase(
|
|
7668
|
+
await listSkillAccountKnowledgeBases(runtime2, origin),
|
|
7669
|
+
knowledgeBaseSelector
|
|
7670
|
+
);
|
|
7671
|
+
return readStatus(runtime2, origin, knowledgeBase.id, importId);
|
|
7672
|
+
}
|
|
7673
|
+
async function poll(runtime2, origin, knowledgeBaseId, importId, waitMs) {
|
|
7674
|
+
const startedAt = runtime2.now().getTime();
|
|
7675
|
+
for (; ; ) {
|
|
7676
|
+
const event = await readStatus(runtime2, origin, knowledgeBaseId, importId);
|
|
7677
|
+
await runtime2.onEvent?.(event);
|
|
7678
|
+
if (event.event !== "processing") return event;
|
|
7679
|
+
if (runtime2.now().getTime() - startedAt >= waitMs) {
|
|
7680
|
+
const detached = { event: "detached", importId };
|
|
7681
|
+
await runtime2.onEvent?.(detached);
|
|
7682
|
+
return detached;
|
|
7683
|
+
}
|
|
7684
|
+
await runtime2.sleep(2e3);
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
async function readStatus(runtime2, origin, knowledgeBaseId, importId) {
|
|
7688
|
+
const current = await withSkillAccountKnowledgeClient(runtime2, origin, (client) => {
|
|
7689
|
+
if (!client.getImportStatus) return unavailableUpload();
|
|
7690
|
+
return client.getImportStatus(knowledgeBaseId, importId);
|
|
7691
|
+
});
|
|
7692
|
+
return {
|
|
7693
|
+
event: current.searchable ? "completed" : current.state === "failed" ? "failed" : "processing",
|
|
7694
|
+
importId,
|
|
7695
|
+
...current.failure ? { failure: current.failure } : {}
|
|
7696
|
+
};
|
|
7697
|
+
}
|
|
7698
|
+
async function inspectFile(filePath) {
|
|
7699
|
+
let metadata;
|
|
7700
|
+
try {
|
|
7701
|
+
metadata = await stat3(filePath);
|
|
7702
|
+
} catch {
|
|
7703
|
+
throw new SkillError("FILE_NOT_FOUND", "The upload file could not be found.", {
|
|
7704
|
+
exitCode: exitCodes.invalidArgument
|
|
7705
|
+
});
|
|
7706
|
+
}
|
|
7707
|
+
if (!metadata.isFile()) {
|
|
7708
|
+
throw new SkillError(
|
|
7709
|
+
"UNSUPPORTED_FILE_TYPE",
|
|
7710
|
+
"Upload accepts one regular file, not a directory or batch.",
|
|
7711
|
+
{ exitCode: exitCodes.invalidArgument }
|
|
7712
|
+
);
|
|
7713
|
+
}
|
|
7714
|
+
return { name: path14.basename(filePath), size: metadata.size };
|
|
7715
|
+
}
|
|
7716
|
+
async function sha256(filePath) {
|
|
7717
|
+
const digest2 = createHash8("sha256");
|
|
7718
|
+
for await (const chunk of createReadStream(filePath)) digest2.update(chunk);
|
|
7719
|
+
return `sha256:${digest2.digest("hex")}`;
|
|
7720
|
+
}
|
|
7721
|
+
function unavailableUpload() {
|
|
7722
|
+
throw new SkillError("PLATFORM_UNAVAILABLE", "SAG upload is unavailable", {
|
|
7723
|
+
exitCode: exitCodes.serviceNotReady
|
|
7724
|
+
});
|
|
7725
|
+
}
|
|
7726
|
+
|
|
7727
|
+
// src/skill/knowledge-client.ts
|
|
7728
|
+
import { z as z14 } from "zod";
|
|
7729
|
+
import { openAsBlob } from "fs";
|
|
7730
|
+
var knowledgeBaseSchema = z14.strictObject({
|
|
7731
|
+
alias: z14.string().min(1).optional(),
|
|
7732
|
+
id: z14.string().min(1),
|
|
7733
|
+
name: z14.string().min(1)
|
|
7734
|
+
});
|
|
7735
|
+
var knowledgeBaseDirectorySchema = z14.strictObject({
|
|
7736
|
+
items: z14.array(knowledgeBaseSchema)
|
|
7737
|
+
});
|
|
7738
|
+
var documentSchema2 = z14.strictObject({
|
|
7739
|
+
id: z14.string().min(1),
|
|
7740
|
+
knowledge_base_id: z14.string().min(1),
|
|
7741
|
+
title: z14.string().min(1),
|
|
7742
|
+
version: z14.number().int().positive()
|
|
7743
|
+
});
|
|
7744
|
+
var documentDirectorySchema = z14.strictObject({
|
|
7745
|
+
items: z14.array(documentSchema2)
|
|
7746
|
+
});
|
|
7747
|
+
var deletedDocumentSchema = z14.strictObject({
|
|
7748
|
+
deleted_at: z14.string().datetime({ offset: true }),
|
|
7749
|
+
document_id: z14.string().min(1),
|
|
7750
|
+
recoverable: z14.literal(true),
|
|
7751
|
+
version: z14.number().int().positive()
|
|
7752
|
+
});
|
|
7753
|
+
var uploadStateSchema = z14.enum([
|
|
7754
|
+
"queued",
|
|
7755
|
+
"accepted",
|
|
7756
|
+
"processing",
|
|
7757
|
+
"completed",
|
|
7758
|
+
"failed"
|
|
7759
|
+
]);
|
|
7760
|
+
var uploadResponseSchema = z14.strictObject({
|
|
7761
|
+
document_id: z14.string().min(1),
|
|
7762
|
+
import_id: z14.string().min(1),
|
|
7763
|
+
job_id: z14.string().min(1),
|
|
7764
|
+
state: uploadStateSchema
|
|
7765
|
+
});
|
|
7766
|
+
var uploadLimitSchema = z14.strictObject({
|
|
7767
|
+
max_upload_bytes: z14.number().int().positive()
|
|
7768
|
+
});
|
|
7769
|
+
var importStatusSchema = z14.strictObject({
|
|
7770
|
+
failure: z14.string().min(1).optional(),
|
|
7771
|
+
searchable: z14.boolean(),
|
|
7772
|
+
state: uploadStateSchema
|
|
7773
|
+
});
|
|
7774
|
+
var answerSchema = z14.strictObject({
|
|
7775
|
+
answer: z14.string().min(1),
|
|
7776
|
+
citations: z14.array(
|
|
7777
|
+
z14.strictObject({
|
|
7778
|
+
number: z14.number().int().positive(),
|
|
7779
|
+
knowledge_base_id: z14.string().min(1),
|
|
7780
|
+
document_id: z14.string().min(1),
|
|
7781
|
+
document_version_id: z14.string().min(1),
|
|
7782
|
+
excerpt: z14.string(),
|
|
7783
|
+
location: z14.string().min(1).optional()
|
|
7784
|
+
})
|
|
7785
|
+
),
|
|
7786
|
+
search_completeness: z14.enum(["complete", "partial"]),
|
|
7787
|
+
search_partial_reasons: z14.array(z14.string().min(1))
|
|
7788
|
+
});
|
|
7789
|
+
var problemSchema2 = z14.object({
|
|
7790
|
+
code: z14.string().min(1),
|
|
7791
|
+
data: z14.unknown().optional()
|
|
7792
|
+
}).passthrough();
|
|
7793
|
+
var duplicateImportDetailsSchema = z14.object({
|
|
7794
|
+
conflict_type: z14.enum([
|
|
7795
|
+
"same_filename_same_checksum",
|
|
7796
|
+
"same_filename_changed_checksum"
|
|
7797
|
+
]),
|
|
7798
|
+
existing_document_id: z14.string().min(1),
|
|
7799
|
+
existing_import_id: z14.string().min(1),
|
|
7800
|
+
existing_filename: z14.string().min(1),
|
|
7801
|
+
current_filename: z14.string().min(1),
|
|
7802
|
+
checksum_sha256: z14.string().min(1),
|
|
7803
|
+
existing_state: z14.string().min(1),
|
|
7804
|
+
overwrite_allowed: z14.boolean()
|
|
7805
|
+
});
|
|
7806
|
+
function httpError(status2, problemCode, problemData) {
|
|
7807
|
+
const normalizedProblemCode = problemCode.toLowerCase();
|
|
7808
|
+
const details = { problemCode };
|
|
7809
|
+
if (normalizedProblemCode === "invalid_grant" || normalizedProblemCode === "expired_token") {
|
|
7810
|
+
return new SkillError("AUTH_EXPIRED", "SAG account authorization has expired", {
|
|
7811
|
+
exitCode: exitCodes.authRequired,
|
|
7812
|
+
...details
|
|
7813
|
+
});
|
|
7814
|
+
}
|
|
7815
|
+
if (normalizedProblemCode === "document_title_conflict") {
|
|
7816
|
+
return new SkillError("DOCUMENT_TITLE_CONFLICT", "The document title conflicts", {
|
|
7817
|
+
exitCode: exitCodes.configConflict,
|
|
7818
|
+
...details
|
|
7819
|
+
});
|
|
7820
|
+
}
|
|
7821
|
+
if (normalizedProblemCode === "document_import_duplicate") {
|
|
7822
|
+
const details2 = duplicateImportDetailsSchema.safeParse(problemData);
|
|
7823
|
+
return new SkillError(
|
|
7824
|
+
"DOCUMENT_IMPORT_DUPLICATE",
|
|
7825
|
+
"A file with the same name already exists in this Knowledge Base",
|
|
7826
|
+
{
|
|
7827
|
+
...details2.success ? { details: details2.data } : {},
|
|
7828
|
+
exitCode: exitCodes.configConflict,
|
|
7829
|
+
problemCode
|
|
7830
|
+
}
|
|
7831
|
+
);
|
|
7832
|
+
}
|
|
7833
|
+
if (normalizedProblemCode === "stale_version") {
|
|
7834
|
+
return new SkillError("STALE_VERSION", "The document version is stale", {
|
|
7835
|
+
exitCode: exitCodes.configConflict,
|
|
7836
|
+
...details
|
|
7837
|
+
});
|
|
7838
|
+
}
|
|
7839
|
+
if (status2 === 401) {
|
|
7840
|
+
return new SkillError("AUTH_REQUIRED", "Skill authorization is required", {
|
|
7841
|
+
exitCode: exitCodes.authRequired,
|
|
7842
|
+
...details
|
|
7843
|
+
});
|
|
7844
|
+
}
|
|
7845
|
+
if (status2 === 403) {
|
|
7846
|
+
return new SkillError("PERMISSION_DENIED", "Skill authorization was denied", {
|
|
7847
|
+
exitCode: exitCodes.permissionDenied,
|
|
7848
|
+
...details
|
|
7849
|
+
});
|
|
7850
|
+
}
|
|
7851
|
+
if (status2 === 404) {
|
|
7852
|
+
return new SkillError(
|
|
7853
|
+
"DOCUMENT_NOT_FOUND",
|
|
7854
|
+
"Skill Knowledge resource was not found",
|
|
7855
|
+
{
|
|
7856
|
+
exitCode: exitCodes.resourceNotFound,
|
|
7857
|
+
...details
|
|
7858
|
+
}
|
|
7859
|
+
);
|
|
7860
|
+
}
|
|
7861
|
+
if (status2 === 409) {
|
|
7862
|
+
return new SkillError("CONFIG_CONFLICT", "Skill Knowledge request conflicts", {
|
|
7863
|
+
exitCode: exitCodes.configConflict,
|
|
7864
|
+
...details
|
|
7865
|
+
});
|
|
7866
|
+
}
|
|
7867
|
+
if (status2 === 429) {
|
|
7868
|
+
return new SkillError(
|
|
7869
|
+
"RATE_LIMITED",
|
|
7870
|
+
"Skill Knowledge service rate limit reached",
|
|
7871
|
+
{
|
|
7872
|
+
exitCode: exitCodes.networkUnreachable,
|
|
7873
|
+
...details
|
|
7874
|
+
}
|
|
7875
|
+
);
|
|
7876
|
+
}
|
|
7877
|
+
if (status2 >= 500) {
|
|
7878
|
+
return new SkillError(
|
|
7879
|
+
"PLATFORM_UNAVAILABLE",
|
|
7880
|
+
"Skill Knowledge service is unavailable",
|
|
7881
|
+
{
|
|
7882
|
+
exitCode: exitCodes.serviceNotReady,
|
|
7883
|
+
...details
|
|
7884
|
+
}
|
|
7885
|
+
);
|
|
7886
|
+
}
|
|
7887
|
+
return new SkillError(
|
|
7888
|
+
"INVALID_RESPONSE",
|
|
7889
|
+
"Skill Knowledge service returned an incompatible response",
|
|
7890
|
+
{
|
|
7891
|
+
exitCode: exitCodes.internalError,
|
|
7892
|
+
...details
|
|
7893
|
+
}
|
|
7894
|
+
);
|
|
7895
|
+
}
|
|
7896
|
+
function throwIfAborted(signal) {
|
|
7897
|
+
if (!signal?.aborted) return;
|
|
7898
|
+
throw signal.reason instanceof Error ? signal.reason : new DOMException("The request was interrupted.", "AbortError");
|
|
7899
|
+
}
|
|
7900
|
+
var SkillKnowledgeHttpClient = class {
|
|
7901
|
+
#accessToken;
|
|
7902
|
+
#fetch;
|
|
7903
|
+
#origin;
|
|
7904
|
+
#timeoutMs;
|
|
7905
|
+
constructor(options) {
|
|
7906
|
+
try {
|
|
7907
|
+
this.#origin = normalizeSkillOrigin(options.origin);
|
|
7908
|
+
} catch {
|
|
7909
|
+
throw new SkillError("CONFIG_CONFLICT", "SAG address is invalid", {
|
|
7910
|
+
exitCode: exitCodes.configConflict
|
|
7911
|
+
});
|
|
7912
|
+
}
|
|
7913
|
+
if (!options.accessToken) {
|
|
7914
|
+
throw new SkillError("AUTH_REQUIRED", "Skill authorization is required", {
|
|
7915
|
+
exitCode: exitCodes.authRequired
|
|
7916
|
+
});
|
|
7917
|
+
}
|
|
7918
|
+
this.#accessToken = options.accessToken;
|
|
7919
|
+
this.#fetch = options.fetchImplementation ?? fetch;
|
|
7920
|
+
this.#timeoutMs = options.timeoutMs ?? 15e3;
|
|
7921
|
+
}
|
|
7922
|
+
async ask(question, knowledgeBaseId) {
|
|
7923
|
+
const answer = this.#parse(
|
|
7924
|
+
answerSchema,
|
|
7925
|
+
await this.#request("/api/v1/integrations/skill/v1/questions", {
|
|
7926
|
+
body: {
|
|
7927
|
+
question,
|
|
7928
|
+
...knowledgeBaseId ? { knowledge_base_id: knowledgeBaseId } : {}
|
|
7929
|
+
},
|
|
7930
|
+
method: "POST"
|
|
7931
|
+
})
|
|
7932
|
+
);
|
|
7933
|
+
return {
|
|
7934
|
+
answer: answer.answer,
|
|
7935
|
+
citations: answer.citations.map((citation) => ({
|
|
7936
|
+
number: citation.number,
|
|
7937
|
+
knowledgeBaseId: citation.knowledge_base_id,
|
|
7938
|
+
documentId: citation.document_id,
|
|
7939
|
+
documentVersionId: citation.document_version_id,
|
|
7940
|
+
excerpt: citation.excerpt,
|
|
7941
|
+
...citation.location ? { location: citation.location } : {}
|
|
7942
|
+
})),
|
|
7943
|
+
completeness: answer.search_completeness,
|
|
7944
|
+
partialReasons: answer.search_partial_reasons
|
|
7945
|
+
};
|
|
7946
|
+
}
|
|
7947
|
+
async listKnowledgeBases() {
|
|
7948
|
+
const parsed = await this.#request(
|
|
7949
|
+
"/api/v1/integrations/skill/v1/knowledge-bases",
|
|
7950
|
+
{
|
|
7951
|
+
method: "GET"
|
|
7952
|
+
}
|
|
7953
|
+
);
|
|
7954
|
+
return this.#parse(knowledgeBaseDirectorySchema, parsed).items.map(
|
|
7955
|
+
(knowledgeBase) => ({
|
|
7956
|
+
id: knowledgeBase.id,
|
|
7957
|
+
name: knowledgeBase.name,
|
|
7958
|
+
...knowledgeBase.alias ? { alias: knowledgeBase.alias } : {}
|
|
7959
|
+
})
|
|
7960
|
+
);
|
|
7961
|
+
}
|
|
7962
|
+
async listDocuments(knowledgeBaseId) {
|
|
7963
|
+
const parsed = await this.#request(
|
|
7964
|
+
`/api/v1/integrations/skill/v1/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/documents`,
|
|
7965
|
+
{ method: "GET" }
|
|
7966
|
+
);
|
|
7967
|
+
return this.#parse(documentDirectorySchema, parsed).items.map((document) => ({
|
|
7968
|
+
id: document.id,
|
|
7969
|
+
knowledgeBaseId: document.knowledge_base_id,
|
|
7970
|
+
title: document.title,
|
|
7971
|
+
version: document.version
|
|
7972
|
+
}));
|
|
7973
|
+
}
|
|
7974
|
+
async renameDocument(input2) {
|
|
7975
|
+
const document = this.#parse(
|
|
7976
|
+
documentSchema2,
|
|
7977
|
+
await this.#request(this.#mutationPath(input2), {
|
|
7978
|
+
body: { title: input2.title },
|
|
7979
|
+
headers: this.#mutationHeaders(input2),
|
|
7980
|
+
method: "PATCH"
|
|
7981
|
+
})
|
|
7982
|
+
);
|
|
7983
|
+
if (document.id !== input2.documentId || document.knowledge_base_id !== input2.knowledgeBaseId) {
|
|
7984
|
+
throw new SkillError(
|
|
7985
|
+
"INVALID_RESPONSE",
|
|
7986
|
+
"Skill Knowledge service returned a different document",
|
|
7987
|
+
{
|
|
7988
|
+
exitCode: exitCodes.internalError
|
|
7989
|
+
}
|
|
7990
|
+
);
|
|
7991
|
+
}
|
|
7992
|
+
return {
|
|
7993
|
+
id: document.id,
|
|
7994
|
+
knowledgeBaseId: document.knowledge_base_id,
|
|
7995
|
+
title: document.title,
|
|
7996
|
+
version: document.version
|
|
7997
|
+
};
|
|
7998
|
+
}
|
|
7999
|
+
async deleteDocument(input2) {
|
|
8000
|
+
const deleted = this.#parse(
|
|
8001
|
+
deletedDocumentSchema,
|
|
8002
|
+
await this.#request(this.#mutationPath(input2), {
|
|
8003
|
+
headers: this.#mutationHeaders(input2),
|
|
8004
|
+
method: "DELETE"
|
|
8005
|
+
})
|
|
8006
|
+
);
|
|
8007
|
+
if (deleted.document_id !== input2.documentId) {
|
|
8008
|
+
throw new SkillError(
|
|
8009
|
+
"INVALID_RESPONSE",
|
|
8010
|
+
"Skill Knowledge service returned a different document",
|
|
8011
|
+
{
|
|
8012
|
+
exitCode: exitCodes.internalError
|
|
8013
|
+
}
|
|
8014
|
+
);
|
|
8015
|
+
}
|
|
8016
|
+
return {
|
|
8017
|
+
deletedAt: deleted.deleted_at,
|
|
8018
|
+
documentId: deleted.document_id,
|
|
8019
|
+
recoverable: deleted.recoverable,
|
|
8020
|
+
version: deleted.version
|
|
8021
|
+
};
|
|
8022
|
+
}
|
|
8023
|
+
async getServerLimit(knowledgeBaseId, signal) {
|
|
8024
|
+
return this.#parse(
|
|
8025
|
+
uploadLimitSchema,
|
|
8026
|
+
await this.#request(
|
|
8027
|
+
`/api/v1/integrations/skill/v1/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/upload-limit`,
|
|
8028
|
+
{ method: "GET", ...signal ? { signal } : {} }
|
|
8029
|
+
)
|
|
8030
|
+
).max_upload_bytes;
|
|
8031
|
+
}
|
|
8032
|
+
async upload(input2, signal) {
|
|
8033
|
+
throwIfAborted(signal);
|
|
8034
|
+
const form = new FormData();
|
|
8035
|
+
form.set("file", await openAsBlob(input2.filePath), input2.fileName);
|
|
8036
|
+
if (input2.overwriteDocumentId) {
|
|
8037
|
+
form.set("overwrite_document_id", input2.overwriteDocumentId);
|
|
8038
|
+
}
|
|
8039
|
+
const payload = await this.#multipartRequest(
|
|
8040
|
+
`/api/v1/integrations/skill/v1/knowledge-bases/${encodeURIComponent(input2.knowledgeBaseId)}/documents`,
|
|
8041
|
+
form,
|
|
8042
|
+
input2.idempotencyKey,
|
|
8043
|
+
signal
|
|
8044
|
+
);
|
|
8045
|
+
const response = this.#parse(uploadResponseSchema, payload);
|
|
8046
|
+
return {
|
|
8047
|
+
documentId: response.document_id,
|
|
8048
|
+
importId: response.import_id,
|
|
8049
|
+
jobId: response.job_id,
|
|
8050
|
+
state: response.state
|
|
8051
|
+
};
|
|
8052
|
+
}
|
|
8053
|
+
async getImportStatus(knowledgeBaseId, importId, signal) {
|
|
8054
|
+
const status2 = this.#parse(
|
|
8055
|
+
importStatusSchema,
|
|
8056
|
+
await this.#request(
|
|
8057
|
+
`/api/v1/integrations/skill/v1/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/imports/${encodeURIComponent(importId)}`,
|
|
8058
|
+
{ method: "GET", ...signal ? { signal } : {} }
|
|
8059
|
+
)
|
|
8060
|
+
);
|
|
8061
|
+
return {
|
|
8062
|
+
searchable: status2.searchable,
|
|
8063
|
+
state: status2.state,
|
|
8064
|
+
...status2.failure ? { failure: status2.failure } : {}
|
|
8065
|
+
};
|
|
8066
|
+
}
|
|
8067
|
+
async #request(path16, options) {
|
|
8068
|
+
let response;
|
|
8069
|
+
try {
|
|
8070
|
+
throwIfAborted(options.signal);
|
|
8071
|
+
response = await this.#fetch(new URL(path16, this.#origin), {
|
|
8072
|
+
headers: {
|
|
8073
|
+
Accept: "application/json",
|
|
8074
|
+
Authorization: `Bearer ${this.#accessToken}`,
|
|
8075
|
+
"Cache-Control": "no-store",
|
|
8076
|
+
...options.headers,
|
|
8077
|
+
...options.body === void 0 ? {} : { "Content-Type": "application/json" }
|
|
8078
|
+
},
|
|
8079
|
+
method: options.method,
|
|
8080
|
+
redirect: "error",
|
|
8081
|
+
signal: this.#requestSignal(options.signal),
|
|
8082
|
+
...options.body === void 0 ? {} : { body: JSON.stringify(options.body) }
|
|
8083
|
+
});
|
|
8084
|
+
} catch (error) {
|
|
8085
|
+
if (options.signal?.aborted) throw error;
|
|
8086
|
+
throw new SkillError(
|
|
8087
|
+
"NETWORK_UNREACHABLE",
|
|
8088
|
+
"Cannot reach the Skill Knowledge service",
|
|
8089
|
+
{
|
|
8090
|
+
exitCode: exitCodes.networkUnreachable
|
|
8091
|
+
}
|
|
8092
|
+
);
|
|
8093
|
+
}
|
|
8094
|
+
if (response.redirected || response.status >= 300 && response.status < 400) {
|
|
8095
|
+
throw new SkillError(
|
|
8096
|
+
"INVALID_RESPONSE",
|
|
8097
|
+
"Skill Knowledge service redirect was rejected",
|
|
8098
|
+
{
|
|
8099
|
+
exitCode: exitCodes.internalError
|
|
8100
|
+
}
|
|
8101
|
+
);
|
|
8102
|
+
}
|
|
8103
|
+
const payload = await response.json().catch(() => void 0);
|
|
8104
|
+
if (!response.ok) {
|
|
8105
|
+
const problem = problemSchema2.safeParse(payload);
|
|
8106
|
+
throw httpError(
|
|
8107
|
+
response.status,
|
|
8108
|
+
problem.success ? problem.data.code : "unknown",
|
|
8109
|
+
problem.success ? problem.data.data : void 0
|
|
8110
|
+
);
|
|
8111
|
+
}
|
|
8112
|
+
return payload;
|
|
8113
|
+
}
|
|
8114
|
+
#mutationPath(input2) {
|
|
8115
|
+
return `/api/v1/integrations/skill/v1/knowledge-bases/${encodeURIComponent(input2.knowledgeBaseId)}/documents/${encodeURIComponent(input2.documentId)}`;
|
|
8116
|
+
}
|
|
8117
|
+
#mutationHeaders(input2) {
|
|
8118
|
+
return {
|
|
8119
|
+
"Idempotency-Key": input2.idempotencyKey,
|
|
8120
|
+
"If-Match": `"document.${input2.documentId}.v${input2.expectedVersion}"`
|
|
8121
|
+
};
|
|
8122
|
+
}
|
|
8123
|
+
async #multipartRequest(path16, body, idempotencyKey, signal) {
|
|
8124
|
+
let response;
|
|
8125
|
+
try {
|
|
8126
|
+
throwIfAborted(signal);
|
|
8127
|
+
response = await this.#fetch(new URL(path16, this.#origin), {
|
|
8128
|
+
headers: {
|
|
8129
|
+
Accept: "application/json",
|
|
8130
|
+
Authorization: `Bearer ${this.#accessToken}`,
|
|
8131
|
+
"Cache-Control": "no-store",
|
|
8132
|
+
"Idempotency-Key": idempotencyKey
|
|
8133
|
+
},
|
|
8134
|
+
method: "POST",
|
|
8135
|
+
redirect: "error",
|
|
8136
|
+
signal: this.#requestSignal(signal),
|
|
8137
|
+
body
|
|
8138
|
+
});
|
|
8139
|
+
} catch (error) {
|
|
8140
|
+
if (signal?.aborted) throw error;
|
|
8141
|
+
throw new SkillError(
|
|
8142
|
+
"NETWORK_UNREACHABLE",
|
|
8143
|
+
"Cannot reach the Skill Knowledge service",
|
|
8144
|
+
{
|
|
8145
|
+
exitCode: exitCodes.networkUnreachable
|
|
8146
|
+
}
|
|
8147
|
+
);
|
|
8148
|
+
}
|
|
8149
|
+
if (response.redirected || response.status >= 300 && response.status < 400) {
|
|
8150
|
+
throw new SkillError(
|
|
8151
|
+
"INVALID_RESPONSE",
|
|
8152
|
+
"Skill Knowledge service redirect was rejected",
|
|
8153
|
+
{
|
|
8154
|
+
exitCode: exitCodes.internalError
|
|
8155
|
+
}
|
|
8156
|
+
);
|
|
8157
|
+
}
|
|
8158
|
+
const payload = await response.json().catch(() => void 0);
|
|
8159
|
+
if (!response.ok) {
|
|
8160
|
+
const problem = problemSchema2.safeParse(payload);
|
|
8161
|
+
throw httpError(
|
|
8162
|
+
response.status,
|
|
8163
|
+
problem.success ? problem.data.code : "unknown",
|
|
8164
|
+
problem.success ? problem.data.data : void 0
|
|
8165
|
+
);
|
|
8166
|
+
}
|
|
8167
|
+
return payload;
|
|
8168
|
+
}
|
|
8169
|
+
#requestSignal(signal) {
|
|
8170
|
+
const timeout = AbortSignal.timeout(this.#timeoutMs);
|
|
8171
|
+
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
8172
|
+
}
|
|
8173
|
+
#parse(schema, payload) {
|
|
8174
|
+
const parsed = schema.safeParse(payload);
|
|
8175
|
+
if (parsed.success) return parsed.data;
|
|
8176
|
+
throw new SkillError(
|
|
8177
|
+
"INVALID_RESPONSE",
|
|
8178
|
+
"Skill Knowledge service returned an incompatible response",
|
|
8179
|
+
{
|
|
8180
|
+
exitCode: exitCodes.internalError
|
|
8181
|
+
}
|
|
8182
|
+
);
|
|
8183
|
+
}
|
|
8184
|
+
};
|
|
8185
|
+
|
|
8186
|
+
// src/program.ts
|
|
8187
|
+
function parseInteger(value) {
|
|
8188
|
+
return Number.parseInt(value, 10);
|
|
8189
|
+
}
|
|
8190
|
+
function parsePositiveInteger(value) {
|
|
8191
|
+
const parsed = Number.parseInt(value, 10);
|
|
8192
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
8193
|
+
throw new CliError("INVALID_ARGUMENT", "\u53C2\u6570\u5FC5\u987B\u662F\u6B63\u6574\u6570", {
|
|
8194
|
+
exitCode: exitCodes.invalidArgument
|
|
8195
|
+
});
|
|
8196
|
+
}
|
|
8197
|
+
return parsed;
|
|
8198
|
+
}
|
|
8199
|
+
function parseNonNegativeInteger(value) {
|
|
8200
|
+
const parsed = Number.parseInt(value, 10);
|
|
8201
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
8202
|
+
throw new CliError("INVALID_ARGUMENT", "\u53C2\u6570\u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570", {
|
|
8203
|
+
exitCode: exitCodes.invalidArgument
|
|
8204
|
+
});
|
|
8205
|
+
}
|
|
8206
|
+
return parsed;
|
|
8207
|
+
}
|
|
8208
|
+
function parseAgent(value) {
|
|
8209
|
+
if (value === "codex" || value === "claude-code") {
|
|
8210
|
+
return value;
|
|
8211
|
+
}
|
|
8212
|
+
throw new CliError("INVALID_ARGUMENT", `Unsupported Agent: ${value}`, {
|
|
8213
|
+
exitCode: exitCodes.invalidArgument,
|
|
8214
|
+
hint: "Choose codex or claude-code."
|
|
8215
|
+
});
|
|
8216
|
+
}
|
|
8217
|
+
function parseEnterpriseAgent(value) {
|
|
8218
|
+
if (value === "codex" || value === "claude-code" || value === "workbuddy") {
|
|
8219
|
+
return value;
|
|
8220
|
+
}
|
|
8221
|
+
throw new CliError("INVALID_ARGUMENT", `\u4E0D\u652F\u6301\u7684 Agent\uFF1A${value}`, {
|
|
8222
|
+
exitCode: exitCodes.invalidArgument,
|
|
8223
|
+
hint: "\u8BF7\u9009\u62E9 codex\u3001claude-code \u6216 workbuddy\u3002"
|
|
8224
|
+
});
|
|
8225
|
+
}
|
|
8226
|
+
function parseCredentialStore(value) {
|
|
8227
|
+
if (value === "keychain" || value === "file") return value;
|
|
8228
|
+
throw new CliError("INVALID_ARGUMENT", `\u4E0D\u652F\u6301\u7684\u51ED\u636E\u5B58\u50A8\uFF1A${value}`, {
|
|
8229
|
+
exitCode: exitCodes.invalidArgument,
|
|
8230
|
+
hint: "\u8BF7\u9009\u62E9 keychain \u6216 file\u3002"
|
|
8231
|
+
});
|
|
8232
|
+
}
|
|
8233
|
+
function resolveEnterpriseSkillAgent(positional, option) {
|
|
8234
|
+
if (positional && option && positional !== option) {
|
|
8235
|
+
throw new CliError(
|
|
8236
|
+
"INVALID_ARGUMENT",
|
|
8237
|
+
"\u4F4D\u7F6E\u53C2\u6570 Agent \u4E0E --agent \u6307\u5B9A\u7684 Agent \u4E0D\u4E00\u81F4",
|
|
8238
|
+
{ exitCode: exitCodes.invalidArgument }
|
|
8239
|
+
);
|
|
8240
|
+
}
|
|
8241
|
+
const agent = option ?? positional;
|
|
8242
|
+
if (!agent) {
|
|
8243
|
+
throw new CliError("INVALID_ARGUMENT", "\u8BF7\u4F7F\u7528 --agent \u6307\u5B9A Agent", {
|
|
8244
|
+
exitCode: exitCodes.invalidArgument,
|
|
8245
|
+
hint: "\u8BF7\u9009\u62E9 codex\u3001claude-code \u6216 workbuddy\u3002"
|
|
8246
|
+
});
|
|
8247
|
+
}
|
|
8248
|
+
return agent;
|
|
8249
|
+
}
|
|
8250
|
+
function parseMcpToolArguments(value) {
|
|
8251
|
+
try {
|
|
8252
|
+
const parsed = JSON.parse(value);
|
|
8253
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8254
|
+
throw new Error("not an object");
|
|
8255
|
+
}
|
|
8256
|
+
return parsed;
|
|
8257
|
+
} catch (cause) {
|
|
8258
|
+
throw new CliError("INVALID_ARGUMENT", "--arguments-json \u5FC5\u987B\u662F\u4E00\u4E2A JSON \u5BF9\u8C61", {
|
|
8259
|
+
exitCode: exitCodes.invalidArgument,
|
|
8260
|
+
cause
|
|
8261
|
+
});
|
|
8262
|
+
}
|
|
8263
|
+
}
|
|
8264
|
+
function collect(value, previous) {
|
|
8265
|
+
return [...previous, value];
|
|
8266
|
+
}
|
|
8267
|
+
function commandOptions2(program) {
|
|
8268
|
+
return program.opts();
|
|
8269
|
+
}
|
|
8270
|
+
var skillWriters = /* @__PURE__ */ new WeakMap();
|
|
8271
|
+
function skillOperation(program) {
|
|
8272
|
+
const [command, subcommand, action] = program.args;
|
|
8273
|
+
if (command === "auth" || command === "skill" && subcommand === "auth") {
|
|
8274
|
+
return "auth";
|
|
8275
|
+
}
|
|
8276
|
+
if (command === "skill" && subcommand === "knowledge") {
|
|
8277
|
+
if (action === "upload" || action === "upload-status") return "upload";
|
|
8278
|
+
if (action === "ask") return "search";
|
|
8279
|
+
return "document";
|
|
8280
|
+
}
|
|
8281
|
+
if (command === "search") return "search";
|
|
8282
|
+
if (command === "document" && (subcommand === "upload" || subcommand === "upload-status")) {
|
|
8283
|
+
return "upload";
|
|
8284
|
+
}
|
|
8285
|
+
if (command === "mcp" || command === "agent") return "install";
|
|
8286
|
+
if (command === "document" || command === "knowledge") return "document";
|
|
8287
|
+
return "install";
|
|
8288
|
+
}
|
|
8289
|
+
function skillData(program, data) {
|
|
8290
|
+
const base = data && typeof data === "object" && !Array.isArray(data) ? data : Array.isArray(data) ? { items: data } : { value: data };
|
|
8291
|
+
if (program.args[0] === "knowledge") {
|
|
8292
|
+
return { resource: "knowledge_base", ...base };
|
|
8293
|
+
}
|
|
8294
|
+
return base;
|
|
8295
|
+
}
|
|
8296
|
+
function commandOutputIsNdjson(arguments_) {
|
|
8297
|
+
return arguments_.some(
|
|
8298
|
+
(argument, index) => argument === "--output=ndjson" || argument === "--output" && arguments_[index + 1] === "ndjson"
|
|
8299
|
+
);
|
|
8300
|
+
}
|
|
8301
|
+
function skillOperationId() {
|
|
8302
|
+
return `cli-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8303
|
+
}
|
|
8304
|
+
function skillEvent(program, event, data, error, operationId = skillOperationId()) {
|
|
8305
|
+
return {
|
|
8306
|
+
schema: "sag.skill.ndjson.v1",
|
|
8307
|
+
operation: skillOperation(program),
|
|
8308
|
+
event,
|
|
8309
|
+
operation_id: operationId,
|
|
8310
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8311
|
+
...data ? { data } : {},
|
|
8312
|
+
...error ? { error } : {}
|
|
8313
|
+
};
|
|
8314
|
+
}
|
|
8315
|
+
function emit(program, dependencies2, data) {
|
|
8316
|
+
const options = commandOptions2(program);
|
|
8317
|
+
if (options.output === "ndjson") {
|
|
8318
|
+
skillWriters.get(program)?.complete(skillEvent(program, "completed", skillData(program, data)));
|
|
8319
|
+
return;
|
|
8320
|
+
}
|
|
8321
|
+
dependencies2.writeStdout(
|
|
8322
|
+
options.json ? renderJson(success3(data)) : renderHuman(data, options.quiet ?? false)
|
|
8323
|
+
);
|
|
8324
|
+
}
|
|
8325
|
+
async function runtime(program, dependencies2) {
|
|
8326
|
+
const options = commandOptions2(program);
|
|
8327
|
+
return createRuntimeContext({
|
|
8328
|
+
options: {
|
|
8329
|
+
...options.url ? { url: options.url } : {},
|
|
8330
|
+
...options.profile ? { profile: options.profile } : {}
|
|
8331
|
+
},
|
|
8332
|
+
environment: dependencies2.environment,
|
|
8333
|
+
configStore: dependencies2.configStore,
|
|
8334
|
+
credentialStore: dependencies2.credentialStore,
|
|
8335
|
+
createClient: dependencies2.createClient
|
|
8336
|
+
});
|
|
8337
|
+
}
|
|
8338
|
+
function localMcpRuntime(dependencies2) {
|
|
8339
|
+
if (!dependencies2.localMcp) {
|
|
8340
|
+
throw new CliError("DEPENDENCY_MISSING", "Local MCP runtime is unavailable", {
|
|
8341
|
+
exitCode: exitCodes.dependencyMissing
|
|
8342
|
+
});
|
|
8343
|
+
}
|
|
8344
|
+
return dependencies2.localMcp;
|
|
8345
|
+
}
|
|
8346
|
+
function enterpriseMcpRuntime(dependencies2) {
|
|
8347
|
+
if (!dependencies2.enterpriseMcp) {
|
|
8348
|
+
throw new CliError("DEPENDENCY_MISSING", "\u4F01\u4E1A MCP \u8FD0\u884C\u73AF\u5883\u4E0D\u53EF\u7528", {
|
|
8349
|
+
exitCode: exitCodes.dependencyMissing
|
|
8350
|
+
});
|
|
8351
|
+
}
|
|
8352
|
+
return dependencies2.enterpriseMcp;
|
|
8353
|
+
}
|
|
8354
|
+
function enterpriseMcpCommandRuntime(dependencies2, timeoutMs) {
|
|
8355
|
+
return {
|
|
8356
|
+
...enterpriseMcpRuntime(dependencies2),
|
|
8357
|
+
cliVersion: dependencies2.version,
|
|
8358
|
+
timeoutMs
|
|
8359
|
+
};
|
|
8360
|
+
}
|
|
8361
|
+
function skillInstallerRuntime(dependencies2) {
|
|
8362
|
+
if (!dependencies2.skill) {
|
|
8363
|
+
throw new CliError(
|
|
8364
|
+
"DEPENDENCY_MISSING",
|
|
8365
|
+
"SAG Knowledge Skill runtime is unavailable",
|
|
8366
|
+
{
|
|
8367
|
+
exitCode: exitCodes.dependencyMissing
|
|
8368
|
+
}
|
|
8369
|
+
);
|
|
8370
|
+
}
|
|
8371
|
+
return dependencies2.skill;
|
|
8372
|
+
}
|
|
8373
|
+
function skillAccountAuthRuntime(dependencies2) {
|
|
8374
|
+
const runtime2 = dependencies2.skillAccount;
|
|
8375
|
+
if (!runtime2) {
|
|
8376
|
+
throw new CliError(
|
|
8377
|
+
"DEPENDENCY_MISSING",
|
|
8378
|
+
"SAG account authorization runtime is unavailable",
|
|
8379
|
+
{ exitCode: exitCodes.dependencyMissing }
|
|
8380
|
+
);
|
|
8381
|
+
}
|
|
8382
|
+
return runtime2;
|
|
8383
|
+
}
|
|
8384
|
+
function skillAccountKnowledgeRuntime(dependencies2) {
|
|
8385
|
+
const account = dependencies2.skillAccount;
|
|
8386
|
+
if (!account) {
|
|
8387
|
+
throw new CliError(
|
|
8388
|
+
"DEPENDENCY_MISSING",
|
|
8389
|
+
"SAG account Knowledge runtime is unavailable",
|
|
8390
|
+
{ exitCode: exitCodes.dependencyMissing }
|
|
8391
|
+
);
|
|
8392
|
+
}
|
|
8393
|
+
return {
|
|
8394
|
+
auth: skillAccountAuthRuntime(dependencies2),
|
|
8395
|
+
createId: randomUUID7,
|
|
8396
|
+
config: account.config,
|
|
8397
|
+
createClient: dependencies2.createSkillKnowledgeClient ?? ((options) => new SkillKnowledgeHttpClient(options))
|
|
8398
|
+
};
|
|
8399
|
+
}
|
|
8400
|
+
function skillAccountUploadRuntime(dependencies2) {
|
|
8401
|
+
return {
|
|
8402
|
+
...skillAccountKnowledgeRuntime(dependencies2),
|
|
8403
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
8404
|
+
sleep: async (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))
|
|
8405
|
+
};
|
|
8406
|
+
}
|
|
8407
|
+
function emitSkillAccountUpload(program, dependencies2, events) {
|
|
8408
|
+
if (commandOptions2(program).output !== "ndjson") {
|
|
8409
|
+
emit(program, dependencies2, { events });
|
|
8410
|
+
return;
|
|
8411
|
+
}
|
|
8412
|
+
const operationId = skillOperationId();
|
|
8413
|
+
const writer = skillWriters.get(program);
|
|
8414
|
+
events.forEach((event, index) => {
|
|
8415
|
+
const data = {
|
|
8416
|
+
import_id: event.importId,
|
|
8417
|
+
...event.documentId ? { document_id: event.documentId } : {},
|
|
8418
|
+
...event.jobId ? { job_id: event.jobId } : {},
|
|
8419
|
+
...event.failure ? { failure: event.failure } : {}
|
|
8420
|
+
};
|
|
8421
|
+
const frame = skillEvent(program, event.event, data, void 0, operationId);
|
|
8422
|
+
if (index === events.length - 1) writer?.complete(frame);
|
|
8423
|
+
else writer?.emit(frame);
|
|
8424
|
+
});
|
|
8425
|
+
}
|
|
8426
|
+
function emitSkillAccountUploadStatus(program, dependencies2, status2) {
|
|
8427
|
+
if (commandOptions2(program).output !== "ndjson") {
|
|
8428
|
+
emitSkillAccountUpload(program, dependencies2, [status2]);
|
|
8429
|
+
return;
|
|
8430
|
+
}
|
|
8431
|
+
skillWriters.get(program)?.complete(
|
|
8432
|
+
skillEvent(program, "completed", {
|
|
8433
|
+
import_id: status2.importId,
|
|
8434
|
+
state: status2.event,
|
|
8435
|
+
...status2.documentId ? { document_id: status2.documentId } : {},
|
|
8436
|
+
...status2.jobId ? { job_id: status2.jobId } : {},
|
|
8437
|
+
...status2.failure ? { failure: status2.failure } : {}
|
|
8438
|
+
})
|
|
8439
|
+
);
|
|
8440
|
+
}
|
|
8441
|
+
async function resolveSkillOrigin(dependencies2, explicitOrigin, promptWhenMissing = false) {
|
|
8442
|
+
if (explicitOrigin) return normalizeSkillOrigin(explicitOrigin);
|
|
8443
|
+
const runtime2 = dependencies2.skillAccount;
|
|
8444
|
+
if (!runtime2) {
|
|
8445
|
+
throw new CliError(
|
|
8446
|
+
"DEPENDENCY_MISSING",
|
|
8447
|
+
"SAG account authorization runtime is unavailable",
|
|
8448
|
+
{ exitCode: exitCodes.dependencyMissing }
|
|
8449
|
+
);
|
|
8450
|
+
}
|
|
8451
|
+
const current = (await runtime2.config.load()).currentOrigin;
|
|
8452
|
+
if (current) return normalizeSkillOrigin(current);
|
|
8453
|
+
if (promptWhenMissing && dependencies2.promptSkillOrigin) {
|
|
8454
|
+
return normalizeSkillOrigin(await dependencies2.promptSkillOrigin());
|
|
8455
|
+
}
|
|
8456
|
+
throw new SkillError("AUTH_REQUIRED", "SAG address is required", {
|
|
8457
|
+
exitCode: exitCodes.authRequired,
|
|
8458
|
+
details: { required_option: "--origin" }
|
|
8459
|
+
});
|
|
8460
|
+
}
|
|
8461
|
+
var ReportedSkillTargetFailure = class extends Error {
|
|
8462
|
+
constructor(exitCode) {
|
|
8463
|
+
super("Skill target operation completed with errors");
|
|
8464
|
+
this.exitCode = exitCode;
|
|
8465
|
+
}
|
|
8466
|
+
exitCode;
|
|
8467
|
+
};
|
|
8468
|
+
function emitSkillTargets(program, dependencies2, report, options = {}) {
|
|
8469
|
+
if (commandOptions2(program).output !== "ndjson") {
|
|
8470
|
+
emit(program, dependencies2, report);
|
|
8471
|
+
return;
|
|
8472
|
+
}
|
|
8473
|
+
const writer = skillWriters.get(program);
|
|
8474
|
+
const operationId = options.operationId ?? skillOperationId();
|
|
8475
|
+
for (const target of report.targets) {
|
|
8476
|
+
const failed3 = target.status === "failed" || target.status === "conflict";
|
|
8477
|
+
writer?.emit(
|
|
8478
|
+
skillEvent(
|
|
8479
|
+
program,
|
|
8480
|
+
failed3 ? "target_failed" : "target_completed",
|
|
8481
|
+
{
|
|
8482
|
+
agent: target.agent,
|
|
8483
|
+
target: target.target,
|
|
8484
|
+
status: target.status,
|
|
8485
|
+
...target.version ? { version: target.version } : {},
|
|
8486
|
+
...target.package ? { package: target.package } : {},
|
|
8487
|
+
...target.code ? { code: target.code } : {}
|
|
8488
|
+
},
|
|
8489
|
+
void 0,
|
|
8490
|
+
operationId
|
|
8491
|
+
)
|
|
8492
|
+
);
|
|
8493
|
+
}
|
|
8494
|
+
const terminal = options.terminal ?? true;
|
|
8495
|
+
const summary = {
|
|
8496
|
+
package: report.package,
|
|
8497
|
+
version: report.version,
|
|
8498
|
+
target_count: report.targets.length
|
|
8499
|
+
};
|
|
8500
|
+
if (!terminal && (!report.completedWithErrors || options.progressOnErrors)) {
|
|
8501
|
+
writer?.emit(
|
|
8502
|
+
skillEvent(
|
|
8503
|
+
program,
|
|
8504
|
+
options.progressEvent ?? "installation_completed",
|
|
8505
|
+
options.progressData ?? summary,
|
|
8506
|
+
void 0,
|
|
8507
|
+
operationId
|
|
8508
|
+
)
|
|
8509
|
+
);
|
|
8510
|
+
return;
|
|
8511
|
+
}
|
|
8512
|
+
writer?.complete(
|
|
8513
|
+
skillEvent(
|
|
8514
|
+
program,
|
|
8515
|
+
report.completedWithErrors ? "completed_with_errors" : "completed",
|
|
8516
|
+
options.terminalData ?? summary,
|
|
8517
|
+
void 0,
|
|
8518
|
+
operationId
|
|
8519
|
+
)
|
|
8520
|
+
);
|
|
8521
|
+
}
|
|
8522
|
+
async function executeSkillTargets(program, dependencies2, report) {
|
|
8523
|
+
const resolved = await report;
|
|
8524
|
+
emitSkillTargets(program, dependencies2, resolved);
|
|
8525
|
+
if (resolved.completedWithErrors) {
|
|
8526
|
+
const conflict6 = resolved.targets.some((target) => target.status === "conflict");
|
|
8527
|
+
throw new ReportedSkillTargetFailure(
|
|
8528
|
+
conflict6 ? exitCodes.configConflict : exitCodes.internalError
|
|
8529
|
+
);
|
|
8530
|
+
}
|
|
8531
|
+
}
|
|
8532
|
+
function rejectEnterpriseProfile(program) {
|
|
8533
|
+
if (!commandOptions2(program).profile) return;
|
|
8534
|
+
throw new CliError("INVALID_ARGUMENT", "\u4F01\u4E1A MCP \u547D\u4EE4\u4E0D\u4F7F\u7528 Profile", {
|
|
8535
|
+
exitCode: exitCodes.invalidArgument,
|
|
8536
|
+
hint: "\u8BF7\u79FB\u9664 --profile\uFF1B\u4E2A\u4EBA Profile \u53EA\u7528\u4E8E sag mcp test \u548C sag agent\u3002"
|
|
8537
|
+
});
|
|
8538
|
+
}
|
|
8539
|
+
async function selectedProfileName(program, dependencies2) {
|
|
8540
|
+
const options = commandOptions2(program);
|
|
8541
|
+
if (options.profile) {
|
|
8542
|
+
await dependencies2.configStore.getProfile(options.profile);
|
|
8543
|
+
return options.profile;
|
|
8544
|
+
}
|
|
8545
|
+
return (await dependencies2.configStore.load()).currentProfile ?? "local";
|
|
8546
|
+
}
|
|
8547
|
+
async function selectedServerName(program, dependencies2, explicitName) {
|
|
8548
|
+
const profile = await selectedProfileName(program, dependencies2);
|
|
8549
|
+
return {
|
|
8550
|
+
profile,
|
|
8551
|
+
serverName: explicitName ?? `sag-knowledge-${profile}`
|
|
8552
|
+
};
|
|
8553
|
+
}
|
|
8554
|
+
function addProfileCommands(program, dependencies2) {
|
|
8555
|
+
const profile = program.command("profile").description("Manage SAG profiles");
|
|
8556
|
+
profile.command("add").argument("<name>").argument("<url>").action(async (name, url) => {
|
|
8557
|
+
emit(program, dependencies2, await dependencies2.configStore.addProfile(name, url));
|
|
8558
|
+
});
|
|
8559
|
+
profile.command("list").action(async () => {
|
|
8560
|
+
emit(program, dependencies2, await dependencies2.configStore.listProfiles());
|
|
8561
|
+
});
|
|
8562
|
+
profile.command("use").argument("<name>").action(async (name) => {
|
|
8563
|
+
emit(program, dependencies2, await dependencies2.configStore.useProfile(name));
|
|
8564
|
+
});
|
|
8565
|
+
profile.command("show").argument("[name]").action(async (name) => {
|
|
8566
|
+
emit(program, dependencies2, await dependencies2.configStore.getProfile(name));
|
|
8567
|
+
});
|
|
8568
|
+
profile.command("remove").argument("<name>").action(async (name) => {
|
|
8569
|
+
if (!commandOptions2(program).yes && !await dependencies2.confirm(`Remove profile ${name}?`)) {
|
|
8570
|
+
emit(program, dependencies2, { removed: null, cancelled: true });
|
|
8571
|
+
return;
|
|
8572
|
+
}
|
|
8573
|
+
await dependencies2.configStore.removeProfile(name);
|
|
8574
|
+
emit(program, dependencies2, { removed: name });
|
|
8575
|
+
});
|
|
8576
|
+
}
|
|
8577
|
+
function addAuthCommands(program, dependencies2) {
|
|
8578
|
+
const auth = program.command("auth").description("Manage SAG credentials");
|
|
8579
|
+
auth.command("login").option("--name <name>", "SAG user name for the existing local login").action(async (options) => {
|
|
8580
|
+
const context = await runtime(program, dependencies2);
|
|
8581
|
+
const result = await login({
|
|
8582
|
+
credentialRef: context.credentialRef,
|
|
8583
|
+
...context.connection.environmentToken ? { environmentToken: context.connection.environmentToken } : {},
|
|
8584
|
+
...options.name ? { name: options.name } : {},
|
|
8585
|
+
promptName: dependencies2.promptName,
|
|
8586
|
+
authenticate: (name) => dependencies2.createClient({ origin: context.connection.url }).login(name),
|
|
8587
|
+
validate: async (token) => dependencies2.createClient({ origin: context.connection.url, token }).me(),
|
|
8588
|
+
store: dependencies2.credentialStore
|
|
8589
|
+
});
|
|
8590
|
+
emit(program, dependencies2, result);
|
|
8591
|
+
});
|
|
8592
|
+
auth.command("status").action(async () => {
|
|
8593
|
+
const context = await runtime(program, dependencies2);
|
|
8594
|
+
const result = await status({
|
|
8595
|
+
credentialRef: context.credentialRef,
|
|
8596
|
+
...context.connection.environmentToken ? { environmentToken: context.connection.environmentToken } : {},
|
|
8597
|
+
validate: async (token) => dependencies2.createClient({ origin: context.connection.url, token }).me(),
|
|
8598
|
+
store: dependencies2.credentialStore
|
|
6395
8599
|
});
|
|
6396
8600
|
emit(program, dependencies2, result);
|
|
6397
8601
|
});
|
|
@@ -6751,7 +8955,7 @@ function addLocalMcpCommands(program, dependencies2) {
|
|
|
6751
8955
|
}
|
|
6752
8956
|
return dependencies2.selectMcpServers(servers);
|
|
6753
8957
|
},
|
|
6754
|
-
selectAgents: async (
|
|
8958
|
+
selectAgents: async (agents2) => dependencies2.selectAgents ? dependencies2.selectAgents(agents2) : [],
|
|
6755
8959
|
selectCollisionAction: async (collision) => {
|
|
6756
8960
|
if (!dependencies2.selectCollisionAction) {
|
|
6757
8961
|
throw new CliError("DEPENDENCY_MISSING", "\u65E0\u6CD5\u5904\u7406\u540C\u540D MCP \u8FDE\u63A5", {
|
|
@@ -6818,15 +9022,258 @@ function addAgentCommands(program, dependencies2) {
|
|
|
6818
9022
|
);
|
|
6819
9023
|
});
|
|
6820
9024
|
}
|
|
9025
|
+
function addSkillCommands(program, dependencies2) {
|
|
9026
|
+
const skill = program.command("skill").description("Install and manage the SAG Knowledge Skill");
|
|
9027
|
+
const selectedAgents = (value, previous) => [
|
|
9028
|
+
...previous,
|
|
9029
|
+
parseEnterpriseAgent(value)
|
|
9030
|
+
];
|
|
9031
|
+
const addInstall = (name, description) => {
|
|
9032
|
+
const command = skill.command(name).description(description).option(
|
|
9033
|
+
"--agent <agent>",
|
|
9034
|
+
"Target codex, claude-code, or workbuddy",
|
|
9035
|
+
selectedAgents,
|
|
9036
|
+
[]
|
|
9037
|
+
);
|
|
9038
|
+
command.option("--origin <origin>", "Full SAG HTTP or HTTPS Origin");
|
|
9039
|
+
command.action(async (options) => {
|
|
9040
|
+
if (options.origin) {
|
|
9041
|
+
const account = dependencies2.skillAccount;
|
|
9042
|
+
if (!account) {
|
|
9043
|
+
throw new CliError(
|
|
9044
|
+
"DEPENDENCY_MISSING",
|
|
9045
|
+
"SAG account configuration is unavailable",
|
|
9046
|
+
{ exitCode: exitCodes.dependencyMissing }
|
|
9047
|
+
);
|
|
9048
|
+
}
|
|
9049
|
+
await account.config.setCurrentOrigin(options.origin);
|
|
9050
|
+
}
|
|
9051
|
+
await executeSkillTargets(
|
|
9052
|
+
program,
|
|
9053
|
+
dependencies2,
|
|
9054
|
+
installSkillTargets(skillInstallerRuntime(dependencies2), {
|
|
9055
|
+
...options.agent.length ? { agents: options.agent } : {},
|
|
9056
|
+
yes: commandOptions2(program).yes ?? false,
|
|
9057
|
+
update: name === "update"
|
|
9058
|
+
})
|
|
9059
|
+
);
|
|
9060
|
+
});
|
|
9061
|
+
};
|
|
9062
|
+
addInstall("install", "Install SAG Knowledge for every detected Agent");
|
|
9063
|
+
addInstall("update", "Resolve the running @latest package and update managed Skills");
|
|
9064
|
+
const auth = skill.command("auth").description("Authorize one SAG account Origin");
|
|
9065
|
+
auth.command("login").description("Open browser approval and authorize the current SAG account").option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").action(async (options) => {
|
|
9066
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin, true);
|
|
9067
|
+
emit(
|
|
9068
|
+
program,
|
|
9069
|
+
dependencies2,
|
|
9070
|
+
await authorizeSkillAccount(skillAccountAuthRuntime(dependencies2), origin)
|
|
9071
|
+
);
|
|
9072
|
+
});
|
|
9073
|
+
auth.command("status").description("Inspect the saved SAG account authorization").option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").action(async (options) => {
|
|
9074
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin);
|
|
9075
|
+
const account = await skillAccountAuthRuntime(dependencies2).config.getAccount(origin);
|
|
9076
|
+
emit(program, dependencies2, {
|
|
9077
|
+
authenticated: Boolean(account),
|
|
9078
|
+
origin,
|
|
9079
|
+
...account ? { user: account.user } : {}
|
|
9080
|
+
});
|
|
9081
|
+
});
|
|
9082
|
+
auth.command("logout").description("Revoke and remove the saved SAG account authorization").option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").action(async (options) => {
|
|
9083
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin);
|
|
9084
|
+
const removed = await logoutSkillAccount(
|
|
9085
|
+
skillAccountAuthRuntime(dependencies2),
|
|
9086
|
+
origin
|
|
9087
|
+
);
|
|
9088
|
+
emit(program, dependencies2, { origin, removed });
|
|
9089
|
+
});
|
|
9090
|
+
const accountKnowledge = skill.command("knowledge").description("Use Knowledge Bases available to the authorized SAG account");
|
|
9091
|
+
accountKnowledge.command("list").description("List Knowledge Bases available to the current SAG account").option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").action(async (options) => {
|
|
9092
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin);
|
|
9093
|
+
emit(
|
|
9094
|
+
program,
|
|
9095
|
+
dependencies2,
|
|
9096
|
+
await listSkillAccountKnowledgeBases(
|
|
9097
|
+
skillAccountKnowledgeRuntime(dependencies2),
|
|
9098
|
+
origin
|
|
9099
|
+
)
|
|
9100
|
+
);
|
|
9101
|
+
});
|
|
9102
|
+
accountKnowledge.command("documents").description("List documents in one exact Knowledge Base").requiredOption(
|
|
9103
|
+
"--knowledge-base <id-or-alias-or-name>",
|
|
9104
|
+
"Exact Knowledge Base ID, alias, or unique name"
|
|
9105
|
+
).option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").action(async (options) => {
|
|
9106
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin);
|
|
9107
|
+
emit(
|
|
9108
|
+
program,
|
|
9109
|
+
dependencies2,
|
|
9110
|
+
await listSkillAccountDocuments(
|
|
9111
|
+
skillAccountKnowledgeRuntime(dependencies2),
|
|
9112
|
+
origin,
|
|
9113
|
+
options.knowledgeBase
|
|
9114
|
+
)
|
|
9115
|
+
);
|
|
9116
|
+
});
|
|
9117
|
+
accountKnowledge.command("ask").description("Ask SAG and relay its answer and citations").argument("<question>", "Question for SAG").option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").option(
|
|
9118
|
+
"--knowledge-base <id-or-alias-or-name>",
|
|
9119
|
+
"Optional exact Knowledge Base scope; omit for SAG global search"
|
|
9120
|
+
).action(
|
|
9121
|
+
async (question, options) => {
|
|
9122
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin);
|
|
9123
|
+
emit(
|
|
9124
|
+
program,
|
|
9125
|
+
dependencies2,
|
|
9126
|
+
await askSkillAccountKnowledge(
|
|
9127
|
+
skillAccountKnowledgeRuntime(dependencies2),
|
|
9128
|
+
origin,
|
|
9129
|
+
question,
|
|
9130
|
+
options.knowledgeBase
|
|
9131
|
+
)
|
|
9132
|
+
);
|
|
9133
|
+
}
|
|
9134
|
+
);
|
|
9135
|
+
accountKnowledge.command("upload").description("Upload one regular file to an exact Knowledge Base").argument("<file>", "One regular file").requiredOption(
|
|
9136
|
+
"--knowledge-base <id-or-alias-or-name>",
|
|
9137
|
+
"Confirmed Knowledge Base ID, alias, or unique name"
|
|
9138
|
+
).option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").option(
|
|
9139
|
+
"--wait <milliseconds>",
|
|
9140
|
+
"Use 0 to detach after acceptance",
|
|
9141
|
+
parseNonNegativeInteger
|
|
9142
|
+
).option(
|
|
9143
|
+
"--overwrite-document-id <document-id>",
|
|
9144
|
+
"Existing document ID returned by a duplicate upload conflict"
|
|
9145
|
+
).action(
|
|
9146
|
+
async (filePath, options) => {
|
|
9147
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin);
|
|
9148
|
+
const events = await uploadSkillAccountFile(
|
|
9149
|
+
skillAccountUploadRuntime(dependencies2),
|
|
9150
|
+
origin,
|
|
9151
|
+
filePath,
|
|
9152
|
+
options.knowledgeBase,
|
|
9153
|
+
options.wait,
|
|
9154
|
+
options.overwriteDocumentId
|
|
9155
|
+
);
|
|
9156
|
+
emitSkillAccountUpload(program, dependencies2, events);
|
|
9157
|
+
}
|
|
9158
|
+
);
|
|
9159
|
+
accountKnowledge.command("upload-status").description("Read upload progress using the returned import ID").argument("<import-id>", "Import ID returned by upload").requiredOption(
|
|
9160
|
+
"--knowledge-base <id-or-alias-or-name>",
|
|
9161
|
+
"Exact Knowledge Base ID, alias, or unique name"
|
|
9162
|
+
).option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").action(
|
|
9163
|
+
async (importId, options) => {
|
|
9164
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin);
|
|
9165
|
+
emitSkillAccountUploadStatus(
|
|
9166
|
+
program,
|
|
9167
|
+
dependencies2,
|
|
9168
|
+
await inspectSkillAccountUpload(
|
|
9169
|
+
skillAccountUploadRuntime(dependencies2),
|
|
9170
|
+
origin,
|
|
9171
|
+
importId,
|
|
9172
|
+
options.knowledgeBase
|
|
9173
|
+
)
|
|
9174
|
+
);
|
|
9175
|
+
}
|
|
9176
|
+
);
|
|
9177
|
+
accountKnowledge.command("rename").description("Rename one exact document version").argument("<document-id>", "Exact document ID").requiredOption("--title <title>", "New document title").requiredOption(
|
|
9178
|
+
"--expected-version <version>",
|
|
9179
|
+
"Current document version",
|
|
9180
|
+
parsePositiveInteger
|
|
9181
|
+
).requiredOption(
|
|
9182
|
+
"--knowledge-base <id-or-alias-or-name>",
|
|
9183
|
+
"Confirmed Knowledge Base ID, alias, or unique name"
|
|
9184
|
+
).option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").action(
|
|
9185
|
+
async (documentId, options) => {
|
|
9186
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin);
|
|
9187
|
+
emit(
|
|
9188
|
+
program,
|
|
9189
|
+
dependencies2,
|
|
9190
|
+
await renameSkillAccountDocument(
|
|
9191
|
+
skillAccountKnowledgeRuntime(dependencies2),
|
|
9192
|
+
origin,
|
|
9193
|
+
{
|
|
9194
|
+
knowledgeBase: options.knowledgeBase,
|
|
9195
|
+
documentId,
|
|
9196
|
+
expectedVersion: options.expectedVersion,
|
|
9197
|
+
title: options.title
|
|
9198
|
+
}
|
|
9199
|
+
)
|
|
9200
|
+
);
|
|
9201
|
+
}
|
|
9202
|
+
);
|
|
9203
|
+
accountKnowledge.command("delete").description("Move one exact document version to the recoverable recycle bin").argument("<document-id>", "Exact document ID").requiredOption(
|
|
9204
|
+
"--expected-version <version>",
|
|
9205
|
+
"Current document version",
|
|
9206
|
+
parsePositiveInteger
|
|
9207
|
+
).requiredOption(
|
|
9208
|
+
"--knowledge-base <id-or-alias-or-name>",
|
|
9209
|
+
"Confirmed Knowledge Base ID, alias, or unique name"
|
|
9210
|
+
).option("--origin <origin>", "Full SAG HTTP or HTTPS Origin").action(
|
|
9211
|
+
async (documentId, options) => {
|
|
9212
|
+
const origin = await resolveSkillOrigin(dependencies2, options.origin);
|
|
9213
|
+
emit(
|
|
9214
|
+
program,
|
|
9215
|
+
dependencies2,
|
|
9216
|
+
await deleteSkillAccountDocument(
|
|
9217
|
+
skillAccountKnowledgeRuntime(dependencies2),
|
|
9218
|
+
origin,
|
|
9219
|
+
{
|
|
9220
|
+
knowledgeBase: options.knowledgeBase,
|
|
9221
|
+
documentId,
|
|
9222
|
+
expectedVersion: options.expectedVersion
|
|
9223
|
+
}
|
|
9224
|
+
)
|
|
9225
|
+
);
|
|
9226
|
+
}
|
|
9227
|
+
);
|
|
9228
|
+
skill.command("status").description("Inspect managed SAG Knowledge Skill targets").option(
|
|
9229
|
+
"--agent <agent>",
|
|
9230
|
+
"Target codex, claude-code, or workbuddy",
|
|
9231
|
+
selectedAgents,
|
|
9232
|
+
[]
|
|
9233
|
+
).action(async (options) => {
|
|
9234
|
+
emitSkillTargets(
|
|
9235
|
+
program,
|
|
9236
|
+
dependencies2,
|
|
9237
|
+
await inspectSkillTargets(skillInstallerRuntime(dependencies2), {
|
|
9238
|
+
...options.agent.length ? { agents: options.agent } : {}
|
|
9239
|
+
})
|
|
9240
|
+
);
|
|
9241
|
+
});
|
|
9242
|
+
skill.command("uninstall").description("Remove unchanged SAG CLI-managed Skills").option(
|
|
9243
|
+
"--agent <agent>",
|
|
9244
|
+
"Target codex, claude-code, or workbuddy",
|
|
9245
|
+
selectedAgents,
|
|
9246
|
+
[]
|
|
9247
|
+
).action(async (options) => {
|
|
9248
|
+
await executeSkillTargets(
|
|
9249
|
+
program,
|
|
9250
|
+
dependencies2,
|
|
9251
|
+
uninstallSkillTargets(skillInstallerRuntime(dependencies2), {
|
|
9252
|
+
...options.agent.length ? { agents: options.agent } : {},
|
|
9253
|
+
yes: commandOptions2(program).yes ?? false
|
|
9254
|
+
})
|
|
9255
|
+
);
|
|
9256
|
+
});
|
|
9257
|
+
}
|
|
6821
9258
|
function createProgram(dependencies2) {
|
|
6822
9259
|
const program = new Command();
|
|
6823
|
-
|
|
9260
|
+
skillWriters.set(program, new SkillEventWriter(dependencies2.writeStdout));
|
|
9261
|
+
program.name("sag").description("Command-line client and diagnostics for SAG").showHelpAfterError().exitOverride().option("--json", "Output a stable JSON envelope").addOption(
|
|
9262
|
+
new Option("--output <format>", "Output format").choices(["human", "ndjson"]).conflicts("json")
|
|
9263
|
+
).option("--quiet", "Output only essential values").option("--profile <name>", "Use a configured SAG profile").option("--url <origin>", "Use a SAG origin without saving it").option("--yes", "Confirm safe local configuration changes").configureOutput({
|
|
6824
9264
|
writeOut: dependencies2.writeStdout,
|
|
6825
9265
|
writeErr: dependencies2.writeStderr
|
|
6826
9266
|
});
|
|
6827
9267
|
program.command("version").action(() => {
|
|
6828
9268
|
const options = commandOptions2(program);
|
|
6829
|
-
if (options.
|
|
9269
|
+
if (options.output === "ndjson") {
|
|
9270
|
+
skillWriters.get(program)?.complete(
|
|
9271
|
+
skillEvent(program, "completed", {
|
|
9272
|
+
name: "sag",
|
|
9273
|
+
version: dependencies2.version
|
|
9274
|
+
})
|
|
9275
|
+
);
|
|
9276
|
+
} else if (options.json) {
|
|
6830
9277
|
dependencies2.writeStdout(
|
|
6831
9278
|
renderJson(success3({ name: "sag", version: dependencies2.version }))
|
|
6832
9279
|
);
|
|
@@ -6840,22 +9287,70 @@ function createProgram(dependencies2) {
|
|
|
6840
9287
|
addKnowledgeCommands(program, dependencies2);
|
|
6841
9288
|
addLocalMcpCommands(program, dependencies2);
|
|
6842
9289
|
addAgentCommands(program, dependencies2);
|
|
9290
|
+
addSkillCommands(program, dependencies2);
|
|
6843
9291
|
return program;
|
|
6844
9292
|
}
|
|
6845
9293
|
async function runCli(arguments_, dependencies2) {
|
|
6846
9294
|
const json = arguments_.includes("--json");
|
|
9295
|
+
const program = createProgram(
|
|
9296
|
+
commandOutputIsNdjson(arguments_) ? { ...dependencies2, writeStderr: () => void 0 } : dependencies2
|
|
9297
|
+
);
|
|
6847
9298
|
try {
|
|
6848
|
-
await
|
|
9299
|
+
await program.parseAsync(arguments_);
|
|
6849
9300
|
return exitCodes.success;
|
|
6850
9301
|
} catch (error) {
|
|
9302
|
+
if (error instanceof ReportedSkillTargetFailure) return error.exitCode;
|
|
6851
9303
|
if (error instanceof CommanderError && error.exitCode === 0) {
|
|
6852
9304
|
return 0;
|
|
6853
9305
|
}
|
|
9306
|
+
if (error instanceof SkillError) {
|
|
9307
|
+
if (json) {
|
|
9308
|
+
dependencies2.writeStdout(
|
|
9309
|
+
renderJson({
|
|
9310
|
+
schema: "sag.cli.v1",
|
|
9311
|
+
ok: false,
|
|
9312
|
+
error: {
|
|
9313
|
+
code: error.code,
|
|
9314
|
+
message: error.message,
|
|
9315
|
+
...error.details ? { details: error.details } : {}
|
|
9316
|
+
}
|
|
9317
|
+
})
|
|
9318
|
+
);
|
|
9319
|
+
} else if (commandOutputIsNdjson(arguments_)) {
|
|
9320
|
+
const event = skillEvent(program, "failed", void 0, {
|
|
9321
|
+
code: error.code,
|
|
9322
|
+
message: error.message,
|
|
9323
|
+
retryable: error.code === "NETWORK_UNREACHABLE",
|
|
9324
|
+
...error.details ? { details: error.details } : {}
|
|
9325
|
+
});
|
|
9326
|
+
const operationId = error.details?.operation_id;
|
|
9327
|
+
skillWriters.get(program)?.complete({
|
|
9328
|
+
...event,
|
|
9329
|
+
...typeof operationId === "string" ? { operation_id: operationId } : {}
|
|
9330
|
+
});
|
|
9331
|
+
} else {
|
|
9332
|
+
const operationId = error.details?.operation_id;
|
|
9333
|
+
dependencies2.writeStderr(
|
|
9334
|
+
`Error [${error.code}]: ${error.message}${typeof operationId === "string" ? `
|
|
9335
|
+
Operation ID: ${operationId}` : ""}
|
|
9336
|
+
`
|
|
9337
|
+
);
|
|
9338
|
+
}
|
|
9339
|
+
return error.exitCode;
|
|
9340
|
+
}
|
|
6854
9341
|
const cliError = error instanceof CommanderError ? new CliError("INVALID_ARGUMENT", error.message, {
|
|
6855
9342
|
exitCode: exitCodes.invalidArgument
|
|
6856
9343
|
}) : toCliError(error);
|
|
6857
9344
|
if (json) {
|
|
6858
9345
|
dependencies2.writeStdout(renderJson(failure(cliError)));
|
|
9346
|
+
} else if (commandOutputIsNdjson(arguments_)) {
|
|
9347
|
+
skillWriters.get(program)?.complete(
|
|
9348
|
+
skillEvent(program, "failed", void 0, {
|
|
9349
|
+
code: toSkillErrorCode(cliError.code),
|
|
9350
|
+
message: cliError.message,
|
|
9351
|
+
retryable: cliError.code === "NETWORK_UNREACHABLE"
|
|
9352
|
+
})
|
|
9353
|
+
);
|
|
6859
9354
|
} else {
|
|
6860
9355
|
dependencies2.writeStderr(
|
|
6861
9356
|
`Error [${cliError.code}]: ${cliError.message}${cliError.hint ? `
|
|
@@ -6886,6 +9381,51 @@ function readStdin() {
|
|
|
6886
9381
|
process.stdin.once("error", reject);
|
|
6887
9382
|
});
|
|
6888
9383
|
}
|
|
9384
|
+
async function resolveLatestSkillBundle(processRunner2) {
|
|
9385
|
+
const directory = await mkdtemp(path15.join(tmpdir(), "sag-skill-latest-"));
|
|
9386
|
+
const cleanup = () => rm3(directory, { recursive: true, force: true });
|
|
9387
|
+
try {
|
|
9388
|
+
const packed = await processRunner2.run({
|
|
9389
|
+
command: "npm",
|
|
9390
|
+
args: [
|
|
9391
|
+
"pack",
|
|
9392
|
+
"@zleap-ai/sag-cli@latest",
|
|
9393
|
+
"--ignore-scripts",
|
|
9394
|
+
"--json",
|
|
9395
|
+
"--pack-destination",
|
|
9396
|
+
directory,
|
|
9397
|
+
"--cache",
|
|
9398
|
+
path15.join(directory, "npm-cache"),
|
|
9399
|
+
"--prefer-online"
|
|
9400
|
+
],
|
|
9401
|
+
timeoutMs: 6e4
|
|
9402
|
+
});
|
|
9403
|
+
if (packed.exitCode !== 0) throw new Error("npm pack failed");
|
|
9404
|
+
const result = JSON.parse(packed.stdout);
|
|
9405
|
+
const filename = Array.isArray(result) && typeof result[0] === "object" && result[0] !== null && typeof result[0].filename === "string" ? result[0].filename : null;
|
|
9406
|
+
if (!filename || path15.basename(filename) !== filename) {
|
|
9407
|
+
throw new Error("npm pack returned an invalid filename");
|
|
9408
|
+
}
|
|
9409
|
+
const extracted = await processRunner2.run({
|
|
9410
|
+
command: "tar",
|
|
9411
|
+
args: ["-xzf", path15.join(directory, filename), "-C", directory],
|
|
9412
|
+
timeoutMs: 3e4
|
|
9413
|
+
});
|
|
9414
|
+
if (extracted.exitCode !== 0) throw new Error("tar extraction failed");
|
|
9415
|
+
return { bundlePath: path15.join(directory, "package", "skill"), cleanup };
|
|
9416
|
+
} catch (cause) {
|
|
9417
|
+
await cleanup();
|
|
9418
|
+
throw new CliError(
|
|
9419
|
+
"NETWORK_UNREACHABLE",
|
|
9420
|
+
"Unable to resolve @latest Skill package",
|
|
9421
|
+
{
|
|
9422
|
+
exitCode: exitCodes.networkUnreachable,
|
|
9423
|
+
cause,
|
|
9424
|
+
hint: "Check npm network access and retry; the installed Skill was not changed."
|
|
9425
|
+
}
|
|
9426
|
+
);
|
|
9427
|
+
}
|
|
9428
|
+
}
|
|
6889
9429
|
var credentialSelection = await createCredentialStore();
|
|
6890
9430
|
if (credentialSelection.warning) {
|
|
6891
9431
|
process.stderr.write(`Warning: ${credentialSelection.warning}
|
|
@@ -6901,6 +9441,16 @@ var adapters = {
|
|
|
6901
9441
|
var enterpriseManaged = new EnterpriseMcpManagedStateStore(
|
|
6902
9442
|
defaultEnterpriseMcpManagedStatePath()
|
|
6903
9443
|
);
|
|
9444
|
+
var skillState2 = new SkillStateStore(defaultSkillStatePath());
|
|
9445
|
+
var skillAccountConfig = new SkillAccountConfigStore(defaultSkillAccountConfigPath());
|
|
9446
|
+
async function openSkillAuthorizationBrowser(processRunner2, verificationUrl) {
|
|
9447
|
+
const command = process.platform === "darwin" ? { command: "open", args: [verificationUrl] } : process.platform === "win32" ? { command: "cmd", args: ["/c", "start", "", verificationUrl] } : { command: "xdg-open", args: [verificationUrl] };
|
|
9448
|
+
const result = await processRunner2.run({
|
|
9449
|
+
...command,
|
|
9450
|
+
timeoutMs: 1e4
|
|
9451
|
+
});
|
|
9452
|
+
if (result.exitCode !== 0) throw new Error("browser opener failed");
|
|
9453
|
+
}
|
|
6904
9454
|
function unavailableEnterpriseKeychain() {
|
|
6905
9455
|
const unavailable = () => {
|
|
6906
9456
|
throw new CliError("DEPENDENCY_MISSING", "\u7CFB\u7EDF\u51ED\u636E\u5E93\u4E0D\u53EF\u7528", {
|
|
@@ -6925,6 +9475,7 @@ var dependencies = {
|
|
|
6925
9475
|
credentialStore: credentialSelection.store,
|
|
6926
9476
|
createClient: defaultClientFactory,
|
|
6927
9477
|
promptName: () => input({ message: "SAG user name" }),
|
|
9478
|
+
promptSkillOrigin: () => input({ message: "SAG address" }),
|
|
6928
9479
|
confirm: (message) => confirm({ message, default: false }),
|
|
6929
9480
|
readStdin,
|
|
6930
9481
|
stdinIsTty: Boolean(process.stdin.isTTY),
|
|
@@ -7002,6 +9553,31 @@ var dependencies = {
|
|
|
7002
9553
|
now: () => /* @__PURE__ */ new Date()
|
|
7003
9554
|
},
|
|
7004
9555
|
now: () => /* @__PURE__ */ new Date()
|
|
9556
|
+
},
|
|
9557
|
+
skill: {
|
|
9558
|
+
home: os5.homedir(),
|
|
9559
|
+
environment: { CODEX_HOME: process.env.CODEX_HOME },
|
|
9560
|
+
state: skillState2,
|
|
9561
|
+
agents: {
|
|
9562
|
+
codex: { id: "codex", installed: () => isAgentInstalled(adapters.codex) },
|
|
9563
|
+
"claude-code": {
|
|
9564
|
+
id: "claude-code",
|
|
9565
|
+
installed: () => isAgentInstalled(adapters["claude-code"])
|
|
9566
|
+
},
|
|
9567
|
+
workbuddy: {
|
|
9568
|
+
id: "workbuddy",
|
|
9569
|
+
installed: () => isAgentInstalled(adapters.workbuddy)
|
|
9570
|
+
}
|
|
9571
|
+
},
|
|
9572
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
9573
|
+
confirm: (message) => confirm({ message, default: false }),
|
|
9574
|
+
resolveLatestBundle: () => resolveLatestSkillBundle(processRunner)
|
|
9575
|
+
},
|
|
9576
|
+
skillAccount: {
|
|
9577
|
+
client: new SkillAccountAuthHttpClient(),
|
|
9578
|
+
config: skillAccountConfig,
|
|
9579
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
9580
|
+
openBrowser: (verificationUrl) => openSkillAuthorizationBrowser(processRunner, verificationUrl)
|
|
7005
9581
|
}
|
|
7006
9582
|
};
|
|
7007
9583
|
process.exitCode = await runCli(process.argv, dependencies);
|