@work-shape/catalyst-cli 0.6.3 → 0.7.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.md +2 -0
- package/dist/index.d.ts +374 -10
- package/dist/index.js +964 -384
- package/package.json +6 -5
- package/skills/catalyst/SKILL.md +84 -0
package/dist/index.js
CHANGED
|
@@ -5,11 +5,6 @@ import { realpathSync } from "fs";
|
|
|
5
5
|
import { parseArgs as parseArgs2 } from "util";
|
|
6
6
|
import { pathToFileURL } from "url";
|
|
7
7
|
|
|
8
|
-
// src/asset-commands.ts
|
|
9
|
-
import { readFile as readFile5 } from "fs/promises";
|
|
10
|
-
import { resolve as resolve5 } from "path";
|
|
11
|
-
import { parseArgs } from "util";
|
|
12
|
-
|
|
13
8
|
// ../asset-schemas/src/asset-types.ts
|
|
14
9
|
var REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"];
|
|
15
10
|
var SKILL_STARTERS_MAX = 2;
|
|
@@ -77,7 +72,7 @@ var PROVIDER_CHECK_ERROR_CLASSES = [
|
|
|
77
72
|
];
|
|
78
73
|
var CONNECTION_NAME_PATTERN = /^[a-z][a-z0-9]*([_-][a-z0-9]+)*$/u;
|
|
79
74
|
var CONNECTION_NAME_MAX_LENGTH = 64;
|
|
80
|
-
var CONNECTION_EXPOSURES = ["agent", "bridge", "workflow"];
|
|
75
|
+
var CONNECTION_EXPOSURES = ["agent", "bridge", "workflow", "outside"];
|
|
81
76
|
var CONNECTION_HEALTH_STATES = [
|
|
82
77
|
"unknown",
|
|
83
78
|
"ok",
|
|
@@ -282,6 +277,9 @@ var agentConfigSchema = z.object({
|
|
|
282
277
|
maxSteps: z.number().int().positive().optional(),
|
|
283
278
|
toolNames: z.array(z.string().min(1)).default([]),
|
|
284
279
|
skillNames: z.array(skillNameSchema).default([]),
|
|
280
|
+
knowledgeBases: z.array(z.string().min(1)).refine((names) => new Set(names).size === names.length, {
|
|
281
|
+
message: "A Knowledge Base is listed once"
|
|
282
|
+
}).optional(),
|
|
285
283
|
initialPrompts: z.array(
|
|
286
284
|
z.object({
|
|
287
285
|
title: localizedStringSchema,
|
|
@@ -939,6 +937,69 @@ function buildPath(pathTemplate, queryParamNames, options = {}) {
|
|
|
939
937
|
return queryString ? `${path}?${queryString}` : path;
|
|
940
938
|
}
|
|
941
939
|
|
|
940
|
+
// ../api-contract/src/openapi.ts
|
|
941
|
+
import { z as z37 } from "zod";
|
|
942
|
+
|
|
943
|
+
// ../api-contract/package.json
|
|
944
|
+
var package_default = {
|
|
945
|
+
name: "@vivd-catalyst/api-contract",
|
|
946
|
+
version: "0.7.0",
|
|
947
|
+
license: "MIT",
|
|
948
|
+
repository: {
|
|
949
|
+
type: "git",
|
|
950
|
+
url: "git+https://github.com/vivd-solutions/vivd-catalyst.git",
|
|
951
|
+
directory: "packages/api-contract"
|
|
952
|
+
},
|
|
953
|
+
type: "module",
|
|
954
|
+
sideEffects: false,
|
|
955
|
+
engines: {
|
|
956
|
+
node: ">=22"
|
|
957
|
+
},
|
|
958
|
+
files: [
|
|
959
|
+
"dist",
|
|
960
|
+
"openapi.json"
|
|
961
|
+
],
|
|
962
|
+
main: "./dist/index.js",
|
|
963
|
+
types: "./dist/index.d.ts",
|
|
964
|
+
exports: {
|
|
965
|
+
".": {
|
|
966
|
+
types: "./dist/index.d.ts",
|
|
967
|
+
development: "./src/index.ts",
|
|
968
|
+
import: "./dist/index.js"
|
|
969
|
+
},
|
|
970
|
+
"./openapi.json": "./openapi.json"
|
|
971
|
+
},
|
|
972
|
+
publishConfig: {
|
|
973
|
+
access: "restricted",
|
|
974
|
+
registry: "https://registry.npmjs.org/",
|
|
975
|
+
exports: {
|
|
976
|
+
".": {
|
|
977
|
+
types: "./dist/index.d.ts",
|
|
978
|
+
import: "./dist/index.js"
|
|
979
|
+
},
|
|
980
|
+
"./openapi.json": "./openapi.json"
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
scripts: {
|
|
984
|
+
build: "tsup src/index.ts --format esm --dts",
|
|
985
|
+
"check:contract": "tsx scripts/check-contract.ts",
|
|
986
|
+
"check:openapi": "tsx scripts/check-openapi.ts",
|
|
987
|
+
"generate:openapi": "tsx scripts/write-openapi.ts",
|
|
988
|
+
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
989
|
+
},
|
|
990
|
+
dependencies: {
|
|
991
|
+
"@vivd-catalyst/asset-schemas": "workspace:*",
|
|
992
|
+
zod: "^4.4.3"
|
|
993
|
+
},
|
|
994
|
+
devDependencies: {
|
|
995
|
+
"@redocly/openapi-core": "^2.62.1",
|
|
996
|
+
"@types/node": "^24.13.1",
|
|
997
|
+
tsup: "^8.5.1",
|
|
998
|
+
tsx: "^4.22.4",
|
|
999
|
+
typescript: "^6.0.3"
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
|
|
942
1003
|
// ../api-contract/src/errors.ts
|
|
943
1004
|
import { z as z6 } from "zod";
|
|
944
1005
|
var appErrorCodeSchema = z6.enum([
|
|
@@ -1498,6 +1559,7 @@ var agentEditableFieldSchema = z9.enum([
|
|
|
1498
1559
|
"maxSteps",
|
|
1499
1560
|
"toolNames",
|
|
1500
1561
|
"skillNames",
|
|
1562
|
+
"knowledgeBases",
|
|
1501
1563
|
"initialPrompts"
|
|
1502
1564
|
]);
|
|
1503
1565
|
var editableAgentFieldsSchema = z9.preprocess((value) => {
|
|
@@ -1731,6 +1793,28 @@ var configAssetRevisionSchema = z9.object({
|
|
|
1731
1793
|
globalVersion: z9.number().int().positive(),
|
|
1732
1794
|
createdAt: timestampSchema
|
|
1733
1795
|
});
|
|
1796
|
+
var toolGroupSchema = z9.enum([
|
|
1797
|
+
"documents",
|
|
1798
|
+
"web",
|
|
1799
|
+
"files",
|
|
1800
|
+
"results",
|
|
1801
|
+
"skills",
|
|
1802
|
+
"platform",
|
|
1803
|
+
"instance"
|
|
1804
|
+
]);
|
|
1805
|
+
var toolTextSchema = z9.object({ en: z9.string(), de: z9.string() });
|
|
1806
|
+
var agentToolReferenceSchema = z9.object({
|
|
1807
|
+
name: z9.string(),
|
|
1808
|
+
title: toolTextSchema.optional(),
|
|
1809
|
+
summary: toolTextSchema.optional(),
|
|
1810
|
+
group: toolGroupSchema.optional(),
|
|
1811
|
+
/** A key of the icon registry of the UI library. */
|
|
1812
|
+
icon: z9.string().optional(),
|
|
1813
|
+
/** Missing for a tool the instance enables and no code registers. */
|
|
1814
|
+
effect: z9.enum(["reading", "changing"]).optional(),
|
|
1815
|
+
/** The module whose switch governs the tool. A core tool has none. */
|
|
1816
|
+
module: z9.string().optional()
|
|
1817
|
+
});
|
|
1734
1818
|
var configAssetReferencesSchema = z9.object({
|
|
1735
1819
|
modelProviderIds: z9.array(z9.string()),
|
|
1736
1820
|
modelBindingIds: z9.array(z9.string()),
|
|
@@ -1745,6 +1829,8 @@ var configAssetReferencesSchema = z9.object({
|
|
|
1745
1829
|
fastModeModelBindingIds: z9.array(z9.string()),
|
|
1746
1830
|
reasoningEfforts: z9.array(reasoningEffortSchema),
|
|
1747
1831
|
enabledToolNames: z9.array(z9.string()),
|
|
1832
|
+
/** The tools of `enabledToolNames`, in their order, with what each tells people. */
|
|
1833
|
+
tools: z9.array(agentToolReferenceSchema).optional(),
|
|
1748
1834
|
/**
|
|
1749
1835
|
* Tools the instance enables whose module is off. No agent can use one. An agent that
|
|
1750
1836
|
* still names one shows it as unavailable, so it can be removed.
|
|
@@ -2866,6 +2952,14 @@ var syncAssetsRequestSchema = z14.object({
|
|
|
2866
2952
|
* instance.
|
|
2867
2953
|
*/
|
|
2868
2954
|
namespace: nameText(z14.string().min(1)).optional(),
|
|
2955
|
+
/**
|
|
2956
|
+
* The workspace that owns every asset the batch names, as a CLI root bound with
|
|
2957
|
+
* `--workspace` sends it. Left out, the batch names the instance's own assets. A batch
|
|
2958
|
+
* names a Namespace or a workspace, never both, and one that names a workspace is no
|
|
2959
|
+
* release: its agents start hidden and its writer holds the rights on what the workspace
|
|
2960
|
+
* owns.
|
|
2961
|
+
*/
|
|
2962
|
+
workspaceId: workspaceIdSchema,
|
|
2869
2963
|
/**
|
|
2870
2964
|
* At most `ASSET_SYNC_MAX_ITEMS` items, or `ASSET_RELEASE_SYNC_MAX_ITEMS` for a holder of
|
|
2871
2965
|
* the release right. A batch without items names the default agent.
|
|
@@ -2964,7 +3058,7 @@ var assetOperations = {
|
|
|
2964
3058
|
id: "assets.sync",
|
|
2965
3059
|
method: "POST",
|
|
2966
3060
|
path: "/api/v1/assets/sync",
|
|
2967
|
-
summary: "Apply puts and deletes in one Namespace, all of them or none",
|
|
3061
|
+
summary: "Apply puts and deletes in one Namespace or workspace, all of them or none",
|
|
2968
3062
|
tag: "Assets",
|
|
2969
3063
|
auth: "principal",
|
|
2970
3064
|
scope: "config_assets:write",
|
|
@@ -5732,8 +5826,99 @@ var jobOperations = {
|
|
|
5732
5826
|
})
|
|
5733
5827
|
};
|
|
5734
5828
|
|
|
5735
|
-
// ../api-contract/src/operations/
|
|
5829
|
+
// ../api-contract/src/operations/knowledge.ts
|
|
5736
5830
|
import { z as z30 } from "zod";
|
|
5831
|
+
var SCALAR_TYPES2 = ["text", "integer", "number", "boolean", "date", "timestamp"];
|
|
5832
|
+
var scalarSchema2 = z30.union([z30.string(), z30.number(), z30.boolean(), z30.null()]);
|
|
5833
|
+
var valueSchema2 = z30.union([scalarSchema2, z30.array(scalarSchema2)]);
|
|
5834
|
+
var knowledgeQueryRequestSchema = z30.strictObject({
|
|
5835
|
+
/** One read-only SELECT or WITH statement with positional placeholders. */
|
|
5836
|
+
sql: z30.string().min(1),
|
|
5837
|
+
/** A bare value, or a value with the type it is bound as. Never part of the statement. */
|
|
5838
|
+
parameters: z30.array(
|
|
5839
|
+
z30.union([
|
|
5840
|
+
valueSchema2,
|
|
5841
|
+
z30.strictObject({
|
|
5842
|
+
type: z30.enum(SCALAR_TYPES2.flatMap((type) => [type, `${type}[]`])),
|
|
5843
|
+
value: valueSchema2
|
|
5844
|
+
})
|
|
5845
|
+
])
|
|
5846
|
+
).optional()
|
|
5847
|
+
});
|
|
5848
|
+
var knowledgeQueryResponseSchema = z30.object({
|
|
5849
|
+
/** `type` is the product's name of the type, `nativeType` the database's. */
|
|
5850
|
+
columns: z30.array(z30.object({ name: z30.string(), type: z30.string(), nativeType: z30.string() })),
|
|
5851
|
+
rows: z30.array(z30.record(z30.string(), z30.unknown())),
|
|
5852
|
+
/** Whether a cap ended the result before its last row. */
|
|
5853
|
+
truncated: z30.boolean(),
|
|
5854
|
+
/** What a citation names: the Knowledge Base and the tables it describes. */
|
|
5855
|
+
source: z30.object({
|
|
5856
|
+
knowledgeBase: z30.string(),
|
|
5857
|
+
title: z30.string(),
|
|
5858
|
+
tables: z30.array(z30.string())
|
|
5859
|
+
})
|
|
5860
|
+
});
|
|
5861
|
+
var knowledgeStateResponseSchema = z30.object({
|
|
5862
|
+
/** The id of the Knowledge Base's asset. */
|
|
5863
|
+
id: z30.string(),
|
|
5864
|
+
/** The id operations and routes name the Knowledge Base by. */
|
|
5865
|
+
name: z30.string(),
|
|
5866
|
+
/** `unchecked` until the published revision was first checked. */
|
|
5867
|
+
status: z30.enum(["unchecked", "ok", "needs_attention"]),
|
|
5868
|
+
/** The revision the last check was of. */
|
|
5869
|
+
revision: z30.number().int().optional(),
|
|
5870
|
+
checkedAt: z30.string().optional(),
|
|
5871
|
+
/** Each described table, and whether the last check could read it. */
|
|
5872
|
+
tables: z30.array(z30.object({ name: z30.string(), readable: z30.boolean() })),
|
|
5873
|
+
/** Why the backend could not be asked at all, as a sentence. */
|
|
5874
|
+
problem: z30.string().optional()
|
|
5875
|
+
});
|
|
5876
|
+
var BASE = {
|
|
5877
|
+
tag: "Knowledge Bases",
|
|
5878
|
+
auth: "principal",
|
|
5879
|
+
requires: [],
|
|
5880
|
+
errors: ["NOT_FOUND", "CONFLICT", "TIMEOUT"]
|
|
5881
|
+
};
|
|
5882
|
+
var knowledgeOperations = {
|
|
5883
|
+
"knowledge_bases.get": defineRegisteredOperation({
|
|
5884
|
+
...BASE,
|
|
5885
|
+
id: "knowledge_bases.get",
|
|
5886
|
+
method: "GET",
|
|
5887
|
+
path: "/api/v1/knowledge-bases/:knowledgeBase",
|
|
5888
|
+
summary: "Read the state of one published Knowledge Base: whether its last check could read every table it describes",
|
|
5889
|
+
scope: "config_assets:read",
|
|
5890
|
+
effect: "reading",
|
|
5891
|
+
response: json(knowledgeStateResponseSchema),
|
|
5892
|
+
errors: ["NOT_FOUND"],
|
|
5893
|
+
rateClass: "read"
|
|
5894
|
+
}),
|
|
5895
|
+
"knowledge_bases.check": defineRegisteredOperation({
|
|
5896
|
+
...BASE,
|
|
5897
|
+
id: "knowledge_bases.check",
|
|
5898
|
+
method: "POST",
|
|
5899
|
+
path: "/api/v1/knowledge-bases/:knowledgeBase/check",
|
|
5900
|
+
summary: "Check one published Knowledge Base now: whether every table it describes can be read. It reads no row",
|
|
5901
|
+
scope: "config_assets:write",
|
|
5902
|
+
effect: "changing",
|
|
5903
|
+
response: json(knowledgeStateResponseSchema),
|
|
5904
|
+
rateClass: "write"
|
|
5905
|
+
}),
|
|
5906
|
+
"knowledge_bases.query": defineRegisteredOperation({
|
|
5907
|
+
...BASE,
|
|
5908
|
+
id: "knowledge_bases.query",
|
|
5909
|
+
method: "POST",
|
|
5910
|
+
path: "/api/v1/knowledge-bases/:knowledgeBase/query",
|
|
5911
|
+
summary: "Read rows of one published Knowledge Base with one read-only statement and bound parameters",
|
|
5912
|
+
scope: "data_store_rows:read",
|
|
5913
|
+
effect: "reading",
|
|
5914
|
+
body: knowledgeQueryRequestSchema,
|
|
5915
|
+
response: json(knowledgeQueryResponseSchema),
|
|
5916
|
+
rateClass: "read"
|
|
5917
|
+
})
|
|
5918
|
+
};
|
|
5919
|
+
|
|
5920
|
+
// ../api-contract/src/operations/operations.ts
|
|
5921
|
+
import { z as z31 } from "zod";
|
|
5737
5922
|
var operationRunOperations = {
|
|
5738
5923
|
"operations.get_run": defineOperation({
|
|
5739
5924
|
id: "operations.get_run",
|
|
@@ -5762,14 +5947,14 @@ var operationRunOperations = {
|
|
|
5762
5947
|
requires: ["audit.view"],
|
|
5763
5948
|
effect: "reading",
|
|
5764
5949
|
query: listQuerySchema.extend({
|
|
5765
|
-
operation:
|
|
5950
|
+
operation: z31.string().min(1).optional(),
|
|
5766
5951
|
/** Only reading calls or only changing ones. Without it the list holds both. */
|
|
5767
|
-
effect:
|
|
5952
|
+
effect: z31.enum(["reading", "changing"]).optional(),
|
|
5768
5953
|
status: operationRunStatusSchema.optional(),
|
|
5769
5954
|
/** The id of the user or service principal that called. */
|
|
5770
|
-
actor:
|
|
5771
|
-
origin:
|
|
5772
|
-
workspaceId:
|
|
5955
|
+
actor: z31.string().min(1).optional(),
|
|
5956
|
+
origin: z31.enum(["agent", "user", "cli", "mcp", "app", "workflow", "schedule"]).optional(),
|
|
5957
|
+
workspaceId: z31.string().min(1).optional(),
|
|
5773
5958
|
/** Runs created at or after this moment. */
|
|
5774
5959
|
since: timestampSchema.optional()
|
|
5775
5960
|
}),
|
|
@@ -5841,74 +6026,74 @@ var pageOperations = {
|
|
|
5841
6026
|
};
|
|
5842
6027
|
|
|
5843
6028
|
// ../api-contract/src/operations/personal-api-keys.ts
|
|
5844
|
-
import { z as
|
|
6029
|
+
import { z as z33 } from "zod";
|
|
5845
6030
|
|
|
5846
6031
|
// ../api-contract/src/personal-api-keys.ts
|
|
5847
|
-
import { z as
|
|
5848
|
-
var personalApiKeyScopeSchema =
|
|
5849
|
-
var personalApiKeySchema =
|
|
5850
|
-
id:
|
|
5851
|
-
ownerUserId:
|
|
6032
|
+
import { z as z32 } from "zod";
|
|
6033
|
+
var personalApiKeyScopeSchema = z32.string().min(1).max(200);
|
|
6034
|
+
var personalApiKeySchema = z32.object({
|
|
6035
|
+
id: z32.string(),
|
|
6036
|
+
ownerUserId: z32.string(),
|
|
5852
6037
|
/** The owner's name, in the instance's list of every person's keys. */
|
|
5853
|
-
ownerDisplayLabel:
|
|
5854
|
-
name:
|
|
6038
|
+
ownerDisplayLabel: z32.string().optional(),
|
|
6039
|
+
name: z32.string(),
|
|
5855
6040
|
/** The start of the key, which a person compares with the key they hold. Never the key. */
|
|
5856
|
-
keyPrefix:
|
|
5857
|
-
scopes:
|
|
6041
|
+
keyPrefix: z32.string(),
|
|
6042
|
+
scopes: z32.array(personalApiKeyScopeSchema),
|
|
5858
6043
|
createdAt: timestampSchema,
|
|
5859
6044
|
expiresAt: timestampSchema,
|
|
5860
6045
|
revokedAt: timestampSchema.optional(),
|
|
5861
6046
|
lastUsedAt: timestampSchema.optional()
|
|
5862
6047
|
});
|
|
5863
|
-
var keyFieldsSchema =
|
|
5864
|
-
name:
|
|
5865
|
-
scopes:
|
|
6048
|
+
var keyFieldsSchema = z32.object({
|
|
6049
|
+
name: z32.string().trim().min(1).max(100),
|
|
6050
|
+
scopes: z32.array(personalApiKeyScopeSchema).min(1).max(64),
|
|
5866
6051
|
/** At most a year ahead. */
|
|
5867
6052
|
expiresAt: timestampSchema
|
|
5868
6053
|
});
|
|
5869
6054
|
var createPersonalApiKeyRequestSchema = keyFieldsSchema;
|
|
5870
|
-
var createPersonalApiKeyResponseSchema =
|
|
6055
|
+
var createPersonalApiKeyResponseSchema = z32.object({
|
|
5871
6056
|
key: personalApiKeySchema,
|
|
5872
6057
|
/** The key, shown this once. Only its hash is stored. */
|
|
5873
|
-
secret:
|
|
6058
|
+
secret: z32.string()
|
|
5874
6059
|
});
|
|
5875
|
-
var startConnectCodeRequestSchema =
|
|
6060
|
+
var startConnectCodeRequestSchema = z32.object({
|
|
5876
6061
|
/** What the CLI calls itself, shown to the person who confirms. Attribution, never authority. */
|
|
5877
|
-
clientLabel:
|
|
6062
|
+
clientLabel: z32.string().trim().min(1).max(100).optional()
|
|
5878
6063
|
});
|
|
5879
|
-
var startConnectCodeResponseSchema =
|
|
6064
|
+
var startConnectCodeResponseSchema = z32.object({
|
|
5880
6065
|
/** The code the person confirms in Settings, as `ABCD-EFGH`. */
|
|
5881
|
-
userCode:
|
|
6066
|
+
userCode: z32.string(),
|
|
5882
6067
|
/** The CLI's own code to collect the key with. It never leaves the CLI. */
|
|
5883
|
-
deviceCode:
|
|
6068
|
+
deviceCode: z32.string(),
|
|
5884
6069
|
expiresAt: timestampSchema,
|
|
5885
6070
|
/** How long the CLI waits between two collection tries. */
|
|
5886
|
-
intervalSeconds:
|
|
6071
|
+
intervalSeconds: z32.number().int().positive(),
|
|
5887
6072
|
/** Where the person confirms the code, relative to the interface's address. */
|
|
5888
|
-
confirmPath:
|
|
6073
|
+
confirmPath: z32.string()
|
|
5889
6074
|
});
|
|
5890
|
-
var redeemConnectCodeRequestSchema =
|
|
5891
|
-
deviceCode:
|
|
6075
|
+
var redeemConnectCodeRequestSchema = z32.object({
|
|
6076
|
+
deviceCode: z32.string().min(1).max(200)
|
|
5892
6077
|
});
|
|
5893
|
-
var redeemConnectCodeResponseSchema =
|
|
6078
|
+
var redeemConnectCodeResponseSchema = z32.discriminatedUnion("status", [
|
|
5894
6079
|
/** Not yet confirmed: try again after the interval. */
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
status:
|
|
6080
|
+
z32.object({ status: z32.literal("pending") }),
|
|
6081
|
+
z32.object({
|
|
6082
|
+
status: z32.literal("issued"),
|
|
5898
6083
|
key: personalApiKeySchema,
|
|
5899
6084
|
/** The key, answered this once. Only its hash is stored. */
|
|
5900
|
-
secret:
|
|
6085
|
+
secret: z32.string()
|
|
5901
6086
|
})
|
|
5902
6087
|
]);
|
|
5903
|
-
var connectCodeSchema =
|
|
5904
|
-
clientLabel:
|
|
6088
|
+
var connectCodeSchema = z32.object({
|
|
6089
|
+
clientLabel: z32.string().optional(),
|
|
5905
6090
|
createdAt: timestampSchema,
|
|
5906
6091
|
expiresAt: timestampSchema
|
|
5907
6092
|
});
|
|
5908
6093
|
var confirmConnectCodeRequestSchema = keyFieldsSchema;
|
|
5909
6094
|
|
|
5910
6095
|
// ../api-contract/src/operations/personal-api-keys.ts
|
|
5911
|
-
var confirmedConnectCodeSchema =
|
|
6096
|
+
var confirmedConnectCodeSchema = z33.object({ confirmed: z33.literal(true) });
|
|
5912
6097
|
var personalApiKeyOperations = {
|
|
5913
6098
|
"me.api_keys.list": defineOperation({
|
|
5914
6099
|
id: "me.api_keys.list",
|
|
@@ -6044,73 +6229,99 @@ var personalApiKeyOperations = {
|
|
|
6044
6229
|
};
|
|
6045
6230
|
|
|
6046
6231
|
// ../api-contract/src/platform.ts
|
|
6047
|
-
import { z as
|
|
6048
|
-
var platformAssetKindSchema =
|
|
6049
|
-
kind:
|
|
6050
|
-
plural:
|
|
6232
|
+
import { z as z34 } from "zod";
|
|
6233
|
+
var platformAssetKindSchema = z34.object({
|
|
6234
|
+
kind: z34.string(),
|
|
6235
|
+
plural: z34.string(),
|
|
6051
6236
|
/**
|
|
6052
6237
|
* `active`: the newest write is what the kind's users are served. `draft_published`: a write
|
|
6053
6238
|
* moves a draft, and `assets.publish` moves what its users are served.
|
|
6054
6239
|
*/
|
|
6055
|
-
lifecycle:
|
|
6240
|
+
lifecycle: z34.enum(["active", "draft_published"]),
|
|
6056
6241
|
/** `config`: a revision is its config. `files`: a revision holds a set of files beside it. */
|
|
6057
|
-
representation:
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6242
|
+
representation: z34.enum(["config", "files"]),
|
|
6243
|
+
/**
|
|
6244
|
+
* Who may own an asset of the kind: the instance, a workspace, or either. An asset a
|
|
6245
|
+
* workspace owns is read and written by naming that workspace.
|
|
6246
|
+
*/
|
|
6247
|
+
owners: z34.array(z34.enum(["instance", "workspace"])),
|
|
6248
|
+
actions: z34.object({
|
|
6249
|
+
read: z34.string(),
|
|
6250
|
+
write: z34.string(),
|
|
6251
|
+
delete: z34.string(),
|
|
6062
6252
|
/** Named by a kind with the lifecycle `draft_published`. */
|
|
6063
|
-
publish:
|
|
6253
|
+
publish: z34.string().optional(),
|
|
6064
6254
|
/**
|
|
6065
6255
|
* Only for a kind whose assets are used through a right of their own: a Connection, or a
|
|
6066
6256
|
* kind whose published revision has users of its own.
|
|
6067
6257
|
*/
|
|
6068
|
-
use:
|
|
6258
|
+
use: z34.string().optional()
|
|
6069
6259
|
}),
|
|
6070
6260
|
/**
|
|
6071
6261
|
* The rights of the kind a service principal can be granted, each on one named asset. Left
|
|
6072
6262
|
* out for a kind that has none. `permissions.grant` hands out these and no other.
|
|
6073
6263
|
*/
|
|
6074
|
-
principalActions:
|
|
6264
|
+
principalActions: z34.array(z34.string()).optional(),
|
|
6075
6265
|
/** The file of a `files` kind that holds its manifest. */
|
|
6076
|
-
manifestFile:
|
|
6266
|
+
manifestFile: z34.string().optional(),
|
|
6077
6267
|
/** What a write of a `files` kind is held to, by the names of the limits. */
|
|
6078
|
-
limits:
|
|
6079
|
-
FILE_SET_MAX_FILES:
|
|
6080
|
-
FILE_SET_MAX_FILE_BYTES:
|
|
6081
|
-
FILE_SET_MAX_TOTAL_BYTES:
|
|
6082
|
-
ASSET_PUT_FILES_MAX_BYTES:
|
|
6083
|
-
ASSET_GET_FILES_MAX_BYTES:
|
|
6268
|
+
limits: z34.object({
|
|
6269
|
+
FILE_SET_MAX_FILES: z34.number().int().positive(),
|
|
6270
|
+
FILE_SET_MAX_FILE_BYTES: z34.number().int().positive(),
|
|
6271
|
+
FILE_SET_MAX_TOTAL_BYTES: z34.number().int().positive(),
|
|
6272
|
+
ASSET_PUT_FILES_MAX_BYTES: z34.number().int().positive(),
|
|
6273
|
+
ASSET_GET_FILES_MAX_BYTES: z34.number().int().positive()
|
|
6084
6274
|
}).optional()
|
|
6085
6275
|
});
|
|
6086
|
-
var platformContextNamespaceSchema =
|
|
6087
|
-
prefix:
|
|
6088
|
-
displayName:
|
|
6089
|
-
actions:
|
|
6276
|
+
var platformContextNamespaceSchema = z34.object({
|
|
6277
|
+
prefix: z34.string(),
|
|
6278
|
+
displayName: z34.string(),
|
|
6279
|
+
actions: z34.array(z34.string())
|
|
6090
6280
|
});
|
|
6091
|
-
var
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6281
|
+
var platformContextWorkspaceSchema = z34.object({
|
|
6282
|
+
id: z34.string(),
|
|
6283
|
+
name: z34.string(),
|
|
6284
|
+
kind: z34.enum(["personal", "shared"]),
|
|
6285
|
+
/** The caller is a member: a new agent it makes there starts offered there. */
|
|
6286
|
+
member: z34.boolean(),
|
|
6287
|
+
actions: z34.array(z34.string())
|
|
6288
|
+
});
|
|
6289
|
+
var platformContextSchema = z34.object({
|
|
6290
|
+
instance: z34.object({ id: z34.string(), name: z34.string() }),
|
|
6291
|
+
release: z34.object({
|
|
6292
|
+
version: z34.string(),
|
|
6293
|
+
/**
|
|
6294
|
+
* The version of `@work-shape/catalyst-cli` on npm that connects a local agent to this
|
|
6295
|
+
* instance. Absent while none is published for it.
|
|
6296
|
+
*/
|
|
6297
|
+
cliVersion: z34.string().optional()
|
|
6298
|
+
}),
|
|
6299
|
+
kinds: z34.array(platformAssetKindSchema),
|
|
6095
6300
|
/**
|
|
6096
6301
|
* The actions of the listed kinds the caller can use on the instance's own assets as a
|
|
6097
6302
|
* whole, whatever their names. A right in one Namespace or on one asset is not among them.
|
|
6098
6303
|
*/
|
|
6099
|
-
actions:
|
|
6304
|
+
actions: z34.array(z34.string()),
|
|
6100
6305
|
/**
|
|
6101
6306
|
* The read actions of the listed kinds a grant allows the caller on one asset or on what one
|
|
6102
6307
|
* workspace owns. Which assets those are, the kind's list says: a deny or a deleted asset can
|
|
6103
6308
|
* leave it empty.
|
|
6104
6309
|
*/
|
|
6105
|
-
assetActions:
|
|
6106
|
-
namespaces:
|
|
6310
|
+
assetActions: z34.array(z34.string()),
|
|
6311
|
+
namespaces: z34.array(platformContextNamespaceSchema),
|
|
6312
|
+
/**
|
|
6313
|
+
* The workspaces whose own assets the caller can use an action of a listed kind on, each
|
|
6314
|
+
* with those actions: through a membership, a grant on the workspace or a right on the
|
|
6315
|
+
* whole instance. A workspace where the caller can use none is left out.
|
|
6316
|
+
*/
|
|
6317
|
+
workspaces: z34.array(platformContextWorkspaceSchema),
|
|
6107
6318
|
/**
|
|
6108
6319
|
* The section rows of the interface's sidebar that are there for the caller, of those that
|
|
6109
6320
|
* belong to no workspace. `workflows`: the module is on, and the caller is listed at least
|
|
6110
6321
|
* one Workflow by `workflows.list` or holds `workflow.write` on the instance, in a Namespace
|
|
6111
6322
|
* or on one Workflow. A member is never shown an empty row; whoever may build one is.
|
|
6112
6323
|
*/
|
|
6113
|
-
visibleSections:
|
|
6324
|
+
visibleSections: z34.array(z34.enum(["workflows"]))
|
|
6114
6325
|
});
|
|
6115
6326
|
|
|
6116
6327
|
// ../api-contract/src/operations/platform.ts
|
|
@@ -6184,32 +6395,32 @@ var referenceOperations = {
|
|
|
6184
6395
|
};
|
|
6185
6396
|
|
|
6186
6397
|
// ../api-contract/src/system.ts
|
|
6187
|
-
import { z as
|
|
6188
|
-
var healthSchema =
|
|
6189
|
-
status:
|
|
6190
|
-
clientInstanceId:
|
|
6398
|
+
import { z as z35 } from "zod";
|
|
6399
|
+
var healthSchema = z35.object({
|
|
6400
|
+
status: z35.literal("ok"),
|
|
6401
|
+
clientInstanceId: z35.string(),
|
|
6191
6402
|
time: timestampSchema
|
|
6192
6403
|
});
|
|
6193
|
-
var readySchema =
|
|
6194
|
-
status:
|
|
6404
|
+
var readySchema = z35.object({
|
|
6405
|
+
status: z35.literal("ready"),
|
|
6195
6406
|
/** The newest migration of this release. */
|
|
6196
|
-
migration:
|
|
6407
|
+
migration: z35.string()
|
|
6197
6408
|
});
|
|
6198
|
-
var notReadySchema =
|
|
6199
|
-
status:
|
|
6200
|
-
reason:
|
|
6201
|
-
missing:
|
|
6409
|
+
var notReadySchema = z35.object({
|
|
6410
|
+
status: z35.literal("not_ready"),
|
|
6411
|
+
reason: z35.enum(["database_unreachable", "database_behind"]),
|
|
6412
|
+
missing: z35.array(z35.string()).optional()
|
|
6202
6413
|
});
|
|
6203
|
-
var readinessSchema =
|
|
6204
|
-
var capturedMailSchema =
|
|
6205
|
-
id:
|
|
6206
|
-
to:
|
|
6207
|
-
email:
|
|
6208
|
-
displayLabel:
|
|
6414
|
+
var readinessSchema = z35.discriminatedUnion("status", [readySchema, notReadySchema]);
|
|
6415
|
+
var capturedMailSchema = z35.object({
|
|
6416
|
+
id: z35.string(),
|
|
6417
|
+
to: z35.object({
|
|
6418
|
+
email: z35.string(),
|
|
6419
|
+
displayLabel: z35.string().optional()
|
|
6209
6420
|
}),
|
|
6210
|
-
subject:
|
|
6211
|
-
text:
|
|
6212
|
-
html:
|
|
6421
|
+
subject: z35.string(),
|
|
6422
|
+
text: z35.string(),
|
|
6423
|
+
html: z35.string(),
|
|
6213
6424
|
sentAt: timestampSchema
|
|
6214
6425
|
});
|
|
6215
6426
|
|
|
@@ -6442,9 +6653,9 @@ var userOperations = {
|
|
|
6442
6653
|
};
|
|
6443
6654
|
|
|
6444
6655
|
// ../api-contract/src/operations/workflows.ts
|
|
6445
|
-
import { z as
|
|
6656
|
+
import { z as z36 } from "zod";
|
|
6446
6657
|
var WORKFLOW_RUN_OUTPUT_PREVIEW_MAX_CHARS = 2e3;
|
|
6447
|
-
var runStatusSchema =
|
|
6658
|
+
var runStatusSchema = z36.enum([
|
|
6448
6659
|
"queued",
|
|
6449
6660
|
"running",
|
|
6450
6661
|
"waiting",
|
|
@@ -6453,8 +6664,8 @@ var runStatusSchema = z35.enum([
|
|
|
6453
6664
|
"cancelled",
|
|
6454
6665
|
"limit_reached"
|
|
6455
6666
|
]);
|
|
6456
|
-
var stepRunStatusSchema =
|
|
6457
|
-
var stepTypeSchema =
|
|
6667
|
+
var stepRunStatusSchema = z36.enum(["queued", "running", "waiting", "done", "failed", "cancelled"]);
|
|
6668
|
+
var stepTypeSchema = z36.enum([
|
|
6458
6669
|
"trigger",
|
|
6459
6670
|
"operation",
|
|
6460
6671
|
"loop",
|
|
@@ -6466,50 +6677,50 @@ var stepTypeSchema = z35.enum([
|
|
|
6466
6677
|
"condition",
|
|
6467
6678
|
"parallel"
|
|
6468
6679
|
]);
|
|
6469
|
-
var countsSchema =
|
|
6470
|
-
total:
|
|
6471
|
-
done:
|
|
6472
|
-
failed:
|
|
6473
|
-
running:
|
|
6680
|
+
var countsSchema = z36.object({
|
|
6681
|
+
total: z36.number().int().min(0),
|
|
6682
|
+
done: z36.number().int().min(0),
|
|
6683
|
+
failed: z36.number().int().min(0),
|
|
6684
|
+
running: z36.number().int().min(0)
|
|
6474
6685
|
});
|
|
6475
|
-
var workflowRunErrorSchema =
|
|
6686
|
+
var workflowRunErrorSchema = z36.object({
|
|
6476
6687
|
/** A code of the engine, or the code of the refusal or failure of the step's operation. */
|
|
6477
|
-
code:
|
|
6688
|
+
code: z36.string(),
|
|
6478
6689
|
/** A sentence of the product. Never a value of the run and never text of an outside system. */
|
|
6479
|
-
message:
|
|
6690
|
+
message: z36.string(),
|
|
6480
6691
|
/** Where the run's principal was refused for a right it does not hold: that right. */
|
|
6481
|
-
action:
|
|
6692
|
+
action: z36.string().optional(),
|
|
6482
6693
|
/** On a run: the step run it ended at. */
|
|
6483
|
-
stepRunId:
|
|
6694
|
+
stepRunId: z36.string().optional()
|
|
6484
6695
|
});
|
|
6485
|
-
var workflowOutputPreviewSchema =
|
|
6696
|
+
var workflowOutputPreviewSchema = z36.object({
|
|
6486
6697
|
/** The start of the value as JSON. */
|
|
6487
|
-
text:
|
|
6488
|
-
truncated:
|
|
6698
|
+
text: z36.string().max(WORKFLOW_RUN_OUTPUT_PREVIEW_MAX_CHARS),
|
|
6699
|
+
truncated: z36.boolean()
|
|
6489
6700
|
});
|
|
6490
|
-
var workflowRunSchema =
|
|
6491
|
-
id:
|
|
6492
|
-
workflow:
|
|
6701
|
+
var workflowRunSchema = z36.object({
|
|
6702
|
+
id: z36.string(),
|
|
6703
|
+
workflow: z36.string(),
|
|
6493
6704
|
/** The revision of the definition the run started on and keeps to its end. */
|
|
6494
|
-
definitionRevisionId:
|
|
6495
|
-
mode:
|
|
6496
|
-
trigger:
|
|
6705
|
+
definitionRevisionId: z36.string(),
|
|
6706
|
+
mode: z36.enum(["live", "test"]),
|
|
6707
|
+
trigger: z36.enum(["manual", "schedule", "parent"]),
|
|
6497
6708
|
status: runStatusSchema,
|
|
6498
6709
|
/**
|
|
6499
6710
|
* The service principal the run acts as. Its rights are the rights of every step. A test run
|
|
6500
6711
|
* (`mode: test`) acts as the person who started it, bounded by this principal.
|
|
6501
6712
|
*/
|
|
6502
|
-
principal:
|
|
6503
|
-
startedBy:
|
|
6504
|
-
kind:
|
|
6505
|
-
id:
|
|
6506
|
-
label:
|
|
6713
|
+
principal: z36.object({ id: z36.string(), name: z36.string(), displayLabel: z36.string() }),
|
|
6714
|
+
startedBy: z36.object({
|
|
6715
|
+
kind: z36.enum(["user", "service_principal"]),
|
|
6716
|
+
id: z36.string(),
|
|
6717
|
+
label: z36.string()
|
|
6507
6718
|
}),
|
|
6508
6719
|
/** The due time of the schedule that started the run. Missing on every other run. */
|
|
6509
6720
|
scheduledFor: timestampSchema.optional(),
|
|
6510
6721
|
/** The Operation Run of the call that started the run. */
|
|
6511
|
-
operationRunId:
|
|
6512
|
-
stepRunCount:
|
|
6722
|
+
operationRunId: z36.string().optional(),
|
|
6723
|
+
stepRunCount: z36.number().int().min(0),
|
|
6513
6724
|
/** After this moment no further step starts and the run ends as `limit_reached`. */
|
|
6514
6725
|
deadlineAt: timestampSchema,
|
|
6515
6726
|
error: workflowRunErrorSchema.optional(),
|
|
@@ -6518,7 +6729,7 @@ var workflowRunSchema = z35.object({
|
|
|
6518
6729
|
startedAt: timestampSchema.optional(),
|
|
6519
6730
|
finishedAt: timestampSchema.optional(),
|
|
6520
6731
|
/** The person who cancelled the run. Missing where a service principal did, and on a run nobody cancelled. */
|
|
6521
|
-
cancelledBy:
|
|
6732
|
+
cancelledBy: z36.string().optional(),
|
|
6522
6733
|
cancelledAt: timestampSchema.optional(),
|
|
6523
6734
|
/**
|
|
6524
6735
|
* When the values of the run were removed: its input and result, and the inputs and outputs
|
|
@@ -6526,98 +6737,98 @@ var workflowRunSchema = z35.object({
|
|
|
6526
6737
|
*/
|
|
6527
6738
|
dataPrunedAt: timestampSchema.optional()
|
|
6528
6739
|
});
|
|
6529
|
-
var workflowStepRunSchema =
|
|
6530
|
-
id:
|
|
6531
|
-
runId:
|
|
6532
|
-
stepId:
|
|
6740
|
+
var workflowStepRunSchema = z36.object({
|
|
6741
|
+
id: z36.string(),
|
|
6742
|
+
runId: z36.string(),
|
|
6743
|
+
stepId: z36.string(),
|
|
6533
6744
|
stepType: stepTypeSchema,
|
|
6534
6745
|
/** The step's path in the run, such as `each_client[3].bookings`. */
|
|
6535
|
-
key:
|
|
6746
|
+
key: z36.string(),
|
|
6536
6747
|
/** The step run of the block that holds the step: today a loop. */
|
|
6537
|
-
containerStepRunId:
|
|
6748
|
+
containerStepRunId: z36.string().optional(),
|
|
6538
6749
|
/** The branch of that block the step stands in. No block with branches runs yet. */
|
|
6539
|
-
branch:
|
|
6750
|
+
branch: z36.string().optional(),
|
|
6540
6751
|
/** Which item of that loop, from 0, and what the loop's `itemLabel` field holds for it. */
|
|
6541
|
-
iteration:
|
|
6752
|
+
iteration: z36.object({ index: z36.number().int().min(0), label: z36.string().optional() }).optional(),
|
|
6542
6753
|
/** On the step run of a loop: how many items it has and how far they are. */
|
|
6543
6754
|
loop: countsSchema.optional(),
|
|
6544
6755
|
/** On the step run of a loop: how the step runs of each step directly inside it stand, by step id. */
|
|
6545
|
-
bodyCounts:
|
|
6756
|
+
bodyCounts: z36.record(z36.string(), countsSchema).optional(),
|
|
6546
6757
|
/**
|
|
6547
6758
|
* On the step run of a loop: how many of its items run at once. `asked` is the loop's
|
|
6548
6759
|
* `concurrency`, an upper bound; `ranWith` is what one process runs of it, which the step
|
|
6549
6760
|
* slots of a process bound.
|
|
6550
6761
|
*/
|
|
6551
|
-
concurrency:
|
|
6762
|
+
concurrency: z36.object({ asked: z36.number().int().min(1), ranWith: z36.number().int().min(1) }).optional(),
|
|
6552
6763
|
/** On the step run of an operation step: what its operation does and what it belongs to. */
|
|
6553
|
-
effect:
|
|
6554
|
-
source:
|
|
6764
|
+
effect: z36.enum(["reading", "changing"]).optional(),
|
|
6765
|
+
source: z36.string().optional(),
|
|
6555
6766
|
/**
|
|
6556
6767
|
* On the step run of an operation step: where its call went (ADR-0031). `live` on every step
|
|
6557
6768
|
* of a run of the published revision; in a test run `dry_run` for a changing step that was
|
|
6558
6769
|
* simulated, `test` for a call to a test target, `live` for a read and for "Run live once".
|
|
6559
6770
|
*/
|
|
6560
|
-
target:
|
|
6771
|
+
target: z36.enum(["live", "test", "dry_run"]).optional(),
|
|
6561
6772
|
/**
|
|
6562
6773
|
* On a step run of a test run: the tester's start satisfied the step's `confirm`, and a run
|
|
6563
6774
|
* of the published revision asks for an approval here.
|
|
6564
6775
|
*/
|
|
6565
|
-
publishedWouldAsk:
|
|
6776
|
+
publishedWouldAsk: z36.enum(["approval"]).optional(),
|
|
6566
6777
|
/**
|
|
6567
6778
|
* On the step run of a code step that ran: what its script wrote to its output and error
|
|
6568
6779
|
* streams beside its result, each cut to 16 KiB.
|
|
6569
6780
|
*/
|
|
6570
|
-
log:
|
|
6781
|
+
log: z36.object({ stdout: z36.string(), stderr: z36.string(), truncated: z36.boolean() }).optional(),
|
|
6571
6782
|
status: stepRunStatusSchema,
|
|
6572
|
-
attempt:
|
|
6783
|
+
attempt: z36.number().int().min(0),
|
|
6573
6784
|
/** The Operation Run of the call an operation step made. */
|
|
6574
|
-
operationRunId:
|
|
6785
|
+
operationRunId: z36.string().optional(),
|
|
6575
6786
|
error: workflowRunErrorSchema.optional(),
|
|
6576
6787
|
outputPreview: workflowOutputPreviewSchema.optional(),
|
|
6577
6788
|
createdAt: timestampSchema,
|
|
6578
6789
|
startedAt: timestampSchema.optional(),
|
|
6579
6790
|
finishedAt: timestampSchema.optional()
|
|
6580
6791
|
});
|
|
6581
|
-
var startWorkflowRunRequestSchema =
|
|
6792
|
+
var startWorkflowRunRequestSchema = z36.object({
|
|
6582
6793
|
/** The input of the run, checked against the `inputs` of the Workflow's definition. */
|
|
6583
|
-
input:
|
|
6794
|
+
input: z36.record(z36.string(), z36.unknown()).optional()
|
|
6584
6795
|
});
|
|
6585
6796
|
var listWorkflowStepRunsQuerySchema = listQuerySchema.extend({
|
|
6586
|
-
topLevel:
|
|
6587
|
-
containerStepRunId:
|
|
6797
|
+
topLevel: z36.enum(["true", "false"]).optional(),
|
|
6798
|
+
containerStepRunId: z36.string().min(1).optional(),
|
|
6588
6799
|
status: stepRunStatusSchema.optional(),
|
|
6589
|
-
iteration:
|
|
6800
|
+
iteration: z36.coerce.number().int().min(0).optional()
|
|
6590
6801
|
});
|
|
6591
|
-
var workflowStartIssueSchema =
|
|
6592
|
-
code:
|
|
6802
|
+
var workflowStartIssueSchema = z36.object({
|
|
6803
|
+
code: z36.string(),
|
|
6593
6804
|
/** A sentence of the product. Never a value of a run. */
|
|
6594
|
-
message:
|
|
6805
|
+
message: z36.string(),
|
|
6595
6806
|
/** Where in the definition it is, such as `steps.check.type`. */
|
|
6596
|
-
path:
|
|
6807
|
+
path: z36.string().optional()
|
|
6597
6808
|
});
|
|
6598
|
-
var workflowSummarySchema =
|
|
6599
|
-
id:
|
|
6600
|
-
name:
|
|
6601
|
-
description:
|
|
6809
|
+
var workflowSummarySchema = z36.object({
|
|
6810
|
+
id: z36.string(),
|
|
6811
|
+
name: z36.string(),
|
|
6812
|
+
description: z36.string().optional(),
|
|
6602
6813
|
/** The published revision: what a run started now has as its `definitionRevisionId`. */
|
|
6603
|
-
definitionRevisionId:
|
|
6814
|
+
definitionRevisionId: z36.string(),
|
|
6604
6815
|
/** Whether a start by hand is accepted now, as far as that is known before its input. */
|
|
6605
|
-
runnable:
|
|
6816
|
+
runnable: z36.boolean(),
|
|
6606
6817
|
/** Why not. Empty exactly where `runnable` is true. */
|
|
6607
|
-
reasons:
|
|
6818
|
+
reasons: z36.array(workflowStartIssueSchema),
|
|
6608
6819
|
/**
|
|
6609
6820
|
* The published revision's `inputs`: the JSON Schema a start's `input` is checked against,
|
|
6610
6821
|
* from which a page builds its start form. Missing where the definition cannot be read.
|
|
6611
6822
|
*/
|
|
6612
|
-
inputs:
|
|
6823
|
+
inputs: z36.record(z36.string(), z36.unknown()).optional(),
|
|
6613
6824
|
/**
|
|
6614
6825
|
* What the caller may do with this Workflow, of `workflow.read` (its runs) and `workflow.use`
|
|
6615
6826
|
* (a start). Never empty: a Workflow the caller holds neither on is not listed.
|
|
6616
6827
|
*/
|
|
6617
|
-
actions:
|
|
6828
|
+
actions: z36.array(z36.string()),
|
|
6618
6829
|
/** The latest run of the Workflow. Missing on one that never ran. */
|
|
6619
|
-
lastRun:
|
|
6620
|
-
id:
|
|
6830
|
+
lastRun: z36.object({
|
|
6831
|
+
id: z36.string(),
|
|
6621
6832
|
status: runStatusSchema,
|
|
6622
6833
|
startedAt: timestampSchema.optional(),
|
|
6623
6834
|
endedAt: timestampSchema.optional()
|
|
@@ -6634,7 +6845,7 @@ var workflowOperations = {
|
|
|
6634
6845
|
scope: "config_assets:read",
|
|
6635
6846
|
requires: [],
|
|
6636
6847
|
effect: "reading",
|
|
6637
|
-
response: json(
|
|
6848
|
+
response: json(z36.object({ items: z36.array(workflowSummarySchema) })),
|
|
6638
6849
|
errors: [],
|
|
6639
6850
|
rateClass: "read"
|
|
6640
6851
|
}),
|
|
@@ -6747,6 +6958,7 @@ var apiOperations = {
|
|
|
6747
6958
|
...guardrailOperations,
|
|
6748
6959
|
...workflowOperations,
|
|
6749
6960
|
...dataStoreOperations,
|
|
6961
|
+
...knowledgeOperations,
|
|
6750
6962
|
...userOperations,
|
|
6751
6963
|
...apiAccessOperations,
|
|
6752
6964
|
...personalApiKeyOperations,
|
|
@@ -6757,11 +6969,20 @@ var apiOperations = {
|
|
|
6757
6969
|
...systemOperations
|
|
6758
6970
|
};
|
|
6759
6971
|
|
|
6972
|
+
// ../api-contract/src/openapi.ts
|
|
6973
|
+
var RELEASE_VERSION = package_default.version;
|
|
6974
|
+
var API_ERROR_CODE_ORDER = appErrorCodeSchema.options;
|
|
6975
|
+
|
|
6976
|
+
// src/asset-commands.ts
|
|
6977
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
6978
|
+
import { resolve as resolve5 } from "path";
|
|
6979
|
+
import { parseArgs } from "util";
|
|
6980
|
+
|
|
6760
6981
|
// src/api.ts
|
|
6761
6982
|
import { randomUUID } from "crypto";
|
|
6762
6983
|
|
|
6763
6984
|
// ../api-client/src/errors.ts
|
|
6764
|
-
import { z as
|
|
6985
|
+
import { z as z38 } from "zod";
|
|
6765
6986
|
var ApiError = class extends Error {
|
|
6766
6987
|
status;
|
|
6767
6988
|
code;
|
|
@@ -6828,7 +7049,7 @@ function readCorrelationId(payload) {
|
|
|
6828
7049
|
return error && "correlationId" in error && typeof error.correlationId === "string" ? error.correlationId : void 0;
|
|
6829
7050
|
}
|
|
6830
7051
|
function readRetryAfterSeconds(payload) {
|
|
6831
|
-
const parsed =
|
|
7052
|
+
const parsed = z38.object({ error: z38.object({ details: rateLimitedDetailsSchema }) }).safeParse(payload);
|
|
6832
7053
|
return parsed.success ? parsed.data.error.details.retryAfterSeconds : void 0;
|
|
6833
7054
|
}
|
|
6834
7055
|
|
|
@@ -7091,6 +7312,7 @@ async function createConfigApi(options) {
|
|
|
7091
7312
|
fetchImpl
|
|
7092
7313
|
});
|
|
7093
7314
|
const sleep = options.sleep ?? defaultSleep;
|
|
7315
|
+
const scope = options.workspaceId === void 0 ? {} : { workspaceId: options.workspaceId };
|
|
7094
7316
|
const change = async (request, unanswered) => {
|
|
7095
7317
|
const idempotencyKey2 = randomUUID();
|
|
7096
7318
|
for (let attempt = 1; ; attempt += 1) {
|
|
@@ -7132,7 +7354,11 @@ async function createConfigApi(options) {
|
|
|
7132
7354
|
sleep,
|
|
7133
7355
|
() => client.assets.list({
|
|
7134
7356
|
params: { kind },
|
|
7135
|
-
query: {
|
|
7357
|
+
query: {
|
|
7358
|
+
limit: MAX_LIST_LIMIT,
|
|
7359
|
+
...scope,
|
|
7360
|
+
...cursor === void 0 ? {} : { cursor }
|
|
7361
|
+
}
|
|
7136
7362
|
})
|
|
7137
7363
|
);
|
|
7138
7364
|
assets.push(...page2.items);
|
|
@@ -7143,7 +7369,10 @@ async function createConfigApi(options) {
|
|
|
7143
7369
|
/** One asset with its content, or nothing where the instance holds none of that name. */
|
|
7144
7370
|
getAsset: async (kind, name) => {
|
|
7145
7371
|
try {
|
|
7146
|
-
return await read(
|
|
7372
|
+
return await read(
|
|
7373
|
+
sleep,
|
|
7374
|
+
() => client.assets.get({ params: { kind, name }, query: { ...scope } })
|
|
7375
|
+
);
|
|
7147
7376
|
} catch (error) {
|
|
7148
7377
|
if (isMissingAsset(error)) return void 0;
|
|
7149
7378
|
throw error;
|
|
@@ -7153,7 +7382,7 @@ async function createConfigApi(options) {
|
|
|
7153
7382
|
getLatestRevision: async (kind, name) => {
|
|
7154
7383
|
const page2 = await read(
|
|
7155
7384
|
sleep,
|
|
7156
|
-
() => client.assets.revisions.list({ params: { kind, name }, query: { limit: 1 } })
|
|
7385
|
+
() => client.assets.revisions.list({ params: { kind, name }, query: { limit: 1, ...scope } })
|
|
7157
7386
|
);
|
|
7158
7387
|
return page2.items[0];
|
|
7159
7388
|
},
|
|
@@ -7222,8 +7451,8 @@ function withoutRedirects(fetchImpl) {
|
|
|
7222
7451
|
function instanceFetch(fetchImpl) {
|
|
7223
7452
|
return withoutRedirects(fetchImpl ?? fetch);
|
|
7224
7453
|
}
|
|
7225
|
-
function isLoopbackHostname(
|
|
7226
|
-
return
|
|
7454
|
+
function isLoopbackHostname(hostname3) {
|
|
7455
|
+
return hostname3 === "localhost" || hostname3 === "::1" || hostname3 === "[::1]" || /^127(?:\.\d{1,3}){3}$/u.test(hostname3);
|
|
7227
7456
|
}
|
|
7228
7457
|
async function exchangeApiKey(fetchImpl, baseUrl, apiKey) {
|
|
7229
7458
|
try {
|
|
@@ -7278,8 +7507,8 @@ function redactSecret(message, secret) {
|
|
|
7278
7507
|
}
|
|
7279
7508
|
|
|
7280
7509
|
// src/asset-sources.ts
|
|
7281
|
-
import { readFile as
|
|
7282
|
-
import { basename, dirname as
|
|
7510
|
+
import { readFile as readFile4, stat as stat3 } from "fs/promises";
|
|
7511
|
+
import { basename, dirname as dirname3, extname, join as join3, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
7283
7512
|
|
|
7284
7513
|
// src/guardrail-files.ts
|
|
7285
7514
|
import { readFile, stat } from "fs/promises";
|
|
@@ -7353,9 +7582,90 @@ function isRecord2(value) {
|
|
|
7353
7582
|
}
|
|
7354
7583
|
|
|
7355
7584
|
// src/working-copy.ts
|
|
7356
|
-
import { mkdir, readFile as
|
|
7357
|
-
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
7585
|
+
import { mkdir as mkdir2, readFile as readFile3, readdir, rename as rename2, rm, rmdir, writeFile as writeFile2 } from "fs/promises";
|
|
7586
|
+
import { dirname as dirname2, isAbsolute, relative, resolve as resolve2, sep } from "path";
|
|
7358
7587
|
import yaml3 from "js-yaml";
|
|
7588
|
+
|
|
7589
|
+
// src/credentials.ts
|
|
7590
|
+
import { chmod, mkdir, readFile as readFile2, rename, stat as stat2, writeFile } from "fs/promises";
|
|
7591
|
+
import { homedir } from "os";
|
|
7592
|
+
import { dirname, join as join2, resolve } from "path";
|
|
7593
|
+
var CREDENTIALS_FILENAME = "credentials.json";
|
|
7594
|
+
function credentialsPath(env) {
|
|
7595
|
+
const folder = env.CATALYST_CONFIG_HOME ?? join2(env.XDG_CONFIG_HOME ?? join2(homedir(), ".config"), "catalyst");
|
|
7596
|
+
return resolve(folder, CREDENTIALS_FILENAME);
|
|
7597
|
+
}
|
|
7598
|
+
function credentialOrigin(url) {
|
|
7599
|
+
return new URL(url).origin;
|
|
7600
|
+
}
|
|
7601
|
+
async function readStoredCredential(env, url) {
|
|
7602
|
+
return (await readCredentialFile(credentialsPath(env))).instances[credentialOrigin(url)];
|
|
7603
|
+
}
|
|
7604
|
+
async function savedInstanceOrigins(env) {
|
|
7605
|
+
return Object.keys((await readCredentialFile(credentialsPath(env))).instances);
|
|
7606
|
+
}
|
|
7607
|
+
async function saveCredential(env, url, credential) {
|
|
7608
|
+
const path = credentialsPath(env);
|
|
7609
|
+
const file = await readCredentialFile(path);
|
|
7610
|
+
file.instances[credentialOrigin(url)] = credential;
|
|
7611
|
+
await writeCredentialFile(path, file);
|
|
7612
|
+
return path;
|
|
7613
|
+
}
|
|
7614
|
+
async function removeCredential(env, url) {
|
|
7615
|
+
const path = credentialsPath(env);
|
|
7616
|
+
const file = await readCredentialFile(path);
|
|
7617
|
+
const origin = credentialOrigin(url);
|
|
7618
|
+
if (file.instances[origin] === void 0) return false;
|
|
7619
|
+
delete file.instances[origin];
|
|
7620
|
+
await writeCredentialFile(path, file);
|
|
7621
|
+
return true;
|
|
7622
|
+
}
|
|
7623
|
+
async function readCredentialFile(path) {
|
|
7624
|
+
let contents;
|
|
7625
|
+
try {
|
|
7626
|
+
contents = await readFile2(path, "utf8");
|
|
7627
|
+
} catch (error) {
|
|
7628
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
7629
|
+
return { version: 1, instances: {} };
|
|
7630
|
+
}
|
|
7631
|
+
throw error;
|
|
7632
|
+
}
|
|
7633
|
+
if (process.platform !== "win32" && ((await stat2(path)).mode & 63) !== 0) {
|
|
7634
|
+
throw new Error(
|
|
7635
|
+
`${path} can be read by other users of this machine. Run 'chmod 600 ${path}', or remove it and run 'catalyst login' again.`
|
|
7636
|
+
);
|
|
7637
|
+
}
|
|
7638
|
+
const parsed = JSON.parse(contents);
|
|
7639
|
+
if (!isCredentialFile(parsed)) {
|
|
7640
|
+
throw new Error(`${path} is not a credentials file of this CLI. Remove it and log in again.`);
|
|
7641
|
+
}
|
|
7642
|
+
return parsed;
|
|
7643
|
+
}
|
|
7644
|
+
async function writeCredentialFile(path, file) {
|
|
7645
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
7646
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
7647
|
+
await writeFile(temporary, `${JSON.stringify(file, null, 2)}
|
|
7648
|
+
`, { mode: 384 });
|
|
7649
|
+
await chmod(temporary, 384);
|
|
7650
|
+
await rename(temporary, path);
|
|
7651
|
+
}
|
|
7652
|
+
function isCredentialFile(value) {
|
|
7653
|
+
if (typeof value !== "object" || value === null) return false;
|
|
7654
|
+
if (!("version" in value) || value.version !== 1) return false;
|
|
7655
|
+
if (!("instances" in value) || typeof value.instances !== "object" || value.instances === null) {
|
|
7656
|
+
return false;
|
|
7657
|
+
}
|
|
7658
|
+
return Object.values(value.instances).every(
|
|
7659
|
+
(entry) => typeof entry === "object" && entry !== null && "apiKey" in entry && typeof entry.apiKey === "string" && "keyId" in entry && typeof entry.keyId === "string" && "name" in entry && typeof entry.name === "string" && "expiresAt" in entry && typeof entry.expiresAt === "string" && "savedAt" in entry && typeof entry.savedAt === "string"
|
|
7660
|
+
);
|
|
7661
|
+
}
|
|
7662
|
+
|
|
7663
|
+
// src/env.ts
|
|
7664
|
+
function readConfigCliEnv(env) {
|
|
7665
|
+
return env ?? process.env;
|
|
7666
|
+
}
|
|
7667
|
+
|
|
7668
|
+
// src/working-copy.ts
|
|
7359
7669
|
var MANIFEST_FILENAME = "catalyst.yaml";
|
|
7360
7670
|
var STATE_FILENAME = ".catalyst-state.json";
|
|
7361
7671
|
var WorkingCopyValidationError = class extends Error {
|
|
@@ -7383,12 +7693,19 @@ function parseManifest(contents, source = MANIFEST_FILENAME) {
|
|
|
7383
7693
|
const namespace = readOptionalString(value, "namespace", `${source} instance '${name}'`);
|
|
7384
7694
|
if (namespace !== void 0 && !NAMESPACE_PREFIX_PATTERN.test(namespace)) {
|
|
7385
7695
|
throw new Error(
|
|
7386
|
-
`${source} instance '${name}' names no Namespace prefix: '${namespace}'. A prefix is lowercase words joined by '-' and ends in '-', such as '
|
|
7696
|
+
`${source} instance '${name}' names no Namespace prefix: '${namespace}'. A prefix is lowercase words joined by '-' and ends in '-', such as 'tax-'.`
|
|
7697
|
+
);
|
|
7698
|
+
}
|
|
7699
|
+
const workspace = readOptionalString(value, "workspace", `${source} instance '${name}'`);
|
|
7700
|
+
if (namespace !== void 0 && workspace !== void 0) {
|
|
7701
|
+
throw new Error(
|
|
7702
|
+
`${source} instance '${name}' names a Namespace and a workspace. A working copy belongs to one of them.`
|
|
7387
7703
|
);
|
|
7388
7704
|
}
|
|
7389
7705
|
instances[name] = {
|
|
7390
7706
|
url: normalizeUrl(value.url, `${source} instance '${name}'`),
|
|
7391
|
-
...namespace === void 0 ? {} : { namespace }
|
|
7707
|
+
...namespace === void 0 ? {} : { namespace },
|
|
7708
|
+
...workspace === void 0 ? {} : { workspace }
|
|
7392
7709
|
};
|
|
7393
7710
|
}
|
|
7394
7711
|
const defaultInstance = readOptionalString(input, "defaultInstance", source);
|
|
@@ -7403,8 +7720,8 @@ function parseManifest(contents, source = MANIFEST_FILENAME) {
|
|
|
7403
7720
|
};
|
|
7404
7721
|
}
|
|
7405
7722
|
async function readManifest(workingDir) {
|
|
7406
|
-
const path =
|
|
7407
|
-
return parseManifest(await
|
|
7723
|
+
const path = resolve2(workingDir, MANIFEST_FILENAME);
|
|
7724
|
+
return parseManifest(await readFile3(path, "utf8"), path);
|
|
7408
7725
|
}
|
|
7409
7726
|
async function readManifestIfPresent(workingDir) {
|
|
7410
7727
|
try {
|
|
@@ -7414,24 +7731,38 @@ async function readManifestIfPresent(workingDir) {
|
|
|
7414
7731
|
throw error;
|
|
7415
7732
|
}
|
|
7416
7733
|
}
|
|
7417
|
-
function resolveInstance(manifest,
|
|
7418
|
-
const key =
|
|
7419
|
-
if (
|
|
7734
|
+
async function resolveInstance(manifest, options = {}) {
|
|
7735
|
+
const key = options.instance ?? manifest?.defaultInstance;
|
|
7736
|
+
if (key !== void 0 && key !== "") {
|
|
7737
|
+
const configured = manifest?.instances[key];
|
|
7738
|
+
if (configured) return { key, ...configured };
|
|
7739
|
+
return { key, url: normalizeUrl(key, `Instance '${key}'`) };
|
|
7740
|
+
}
|
|
7741
|
+
const refusal = `No instance selected; pass --instance or set defaultInstance in ${MANIFEST_FILENAME}.`;
|
|
7742
|
+
if (manifest !== void 0) {
|
|
7743
|
+
const names = Object.keys(manifest.instances);
|
|
7420
7744
|
throw new Error(
|
|
7421
|
-
|
|
7745
|
+
names.length === 0 ? refusal : `${refusal} ${MANIFEST_FILENAME} names ${names.join(", ")}.`
|
|
7422
7746
|
);
|
|
7423
7747
|
}
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7748
|
+
if (readConfigCliEnv(options.env).CATALYST_API_KEY) {
|
|
7749
|
+
throw new Error(`${refusal} CATALYST_API_KEY is set: name the instance it belongs to.`);
|
|
7750
|
+
}
|
|
7751
|
+
const saved = await savedInstanceOrigins(readConfigCliEnv(options.env));
|
|
7752
|
+
const [origin, ...more] = saved;
|
|
7753
|
+
if (origin === void 0) {
|
|
7754
|
+
throw new Error(
|
|
7755
|
+
`${refusal} No key is saved; 'catalyst login --instance <https-url>' saves one.`
|
|
7756
|
+
);
|
|
7427
7757
|
}
|
|
7428
|
-
|
|
7758
|
+
if (more.length > 0) throw new Error(`${refusal} Keys are saved for ${saved.join(", ")}.`);
|
|
7759
|
+
return { key: origin, url: origin };
|
|
7429
7760
|
}
|
|
7430
7761
|
var ASSET_KEY_PATTERN = /^(agent|skill|guardrail):.+$/u;
|
|
7431
7762
|
async function readStateFile(path) {
|
|
7432
7763
|
let contents;
|
|
7433
7764
|
try {
|
|
7434
|
-
contents = await
|
|
7765
|
+
contents = await readFile3(path, "utf8");
|
|
7435
7766
|
} catch (error) {
|
|
7436
7767
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
7437
7768
|
return { instances: {} };
|
|
@@ -7491,11 +7822,11 @@ async function readStateFile(path) {
|
|
|
7491
7822
|
return { instances };
|
|
7492
7823
|
}
|
|
7493
7824
|
async function writeStateFile(path, state) {
|
|
7494
|
-
await
|
|
7825
|
+
await mkdir2(dirname2(path), { recursive: true });
|
|
7495
7826
|
const temporaryPath = `${path}.${process.pid}.tmp`;
|
|
7496
|
-
await
|
|
7827
|
+
await writeFile2(temporaryPath, `${JSON.stringify(state, null, 2)}
|
|
7497
7828
|
`, "utf8");
|
|
7498
|
-
await
|
|
7829
|
+
await rename2(temporaryPath, path);
|
|
7499
7830
|
}
|
|
7500
7831
|
async function readWorkingCopy(workingDir, manifest) {
|
|
7501
7832
|
const [agentFiles, skillFiles, guardrailFiles] = await Promise.all([
|
|
@@ -7510,7 +7841,7 @@ async function readWorkingCopy(workingDir, manifest) {
|
|
|
7510
7841
|
for (const path of agentFiles) {
|
|
7511
7842
|
const file = toRelativePath(workingDir, path);
|
|
7512
7843
|
try {
|
|
7513
|
-
const input = loadYamlData(await
|
|
7844
|
+
const input = loadYamlData(await readFile3(path, "utf8"), file);
|
|
7514
7845
|
const parsed = agentConfigSchema.safeParse(input);
|
|
7515
7846
|
if (parsed.success) {
|
|
7516
7847
|
agents.push(parsed.data);
|
|
@@ -7524,7 +7855,7 @@ async function readWorkingCopy(workingDir, manifest) {
|
|
|
7524
7855
|
for (const path of skillFiles) {
|
|
7525
7856
|
const file = toRelativePath(workingDir, path);
|
|
7526
7857
|
try {
|
|
7527
|
-
const contents = await
|
|
7858
|
+
const contents = await readFile3(path, "utf8");
|
|
7528
7859
|
const parsedMarkdown = parseSkillMarkdown(contents, file, toPosixPath(path));
|
|
7529
7860
|
const resources = await readSkillResources(path);
|
|
7530
7861
|
const parsed = skillConfigSchema.safeParse(
|
|
@@ -7543,7 +7874,7 @@ async function readWorkingCopy(workingDir, manifest) {
|
|
|
7543
7874
|
for (const path of guardrailFiles) {
|
|
7544
7875
|
const file = toRelativePath(workingDir, path);
|
|
7545
7876
|
try {
|
|
7546
|
-
guardrails.push(await readGuardrailFolder(
|
|
7877
|
+
guardrails.push(await readGuardrailFolder(dirname2(path)));
|
|
7547
7878
|
} catch (error) {
|
|
7548
7879
|
const extracted = extractErrorIssues(file, error);
|
|
7549
7880
|
issues.push(...extracted.length > 0 ? extracted : [{ file, message: errorMessage(error) }]);
|
|
@@ -7583,18 +7914,18 @@ function matchesManifestPath(root, path, patterns) {
|
|
|
7583
7914
|
}
|
|
7584
7915
|
function agentAssetPath(workingDir, name) {
|
|
7585
7916
|
assertSafeAssetName(name);
|
|
7586
|
-
return
|
|
7917
|
+
return resolve2(workingDir, "agents", `${name}.agent.yaml`);
|
|
7587
7918
|
}
|
|
7588
7919
|
function skillAssetPath(workingDir, name) {
|
|
7589
7920
|
assertSafeAssetName(name);
|
|
7590
|
-
return
|
|
7921
|
+
return resolve2(workingDir, "skills", name, "SKILL.md");
|
|
7591
7922
|
}
|
|
7592
7923
|
function guardrailAssetPath(workingDir, name) {
|
|
7593
7924
|
assertSafeAssetName(name);
|
|
7594
|
-
return
|
|
7925
|
+
return resolve2(workingDir, "guardrails", name, GUARDRAIL_FILE);
|
|
7595
7926
|
}
|
|
7596
7927
|
async function readSkillResources(skillFilePath) {
|
|
7597
|
-
const skillDirectory =
|
|
7928
|
+
const skillDirectory = dirname2(skillFilePath);
|
|
7598
7929
|
const files = [];
|
|
7599
7930
|
await walkFiles(skillDirectory, skillDirectory, files);
|
|
7600
7931
|
const resources = [];
|
|
@@ -7609,7 +7940,7 @@ async function readSkillResources(skillFilePath) {
|
|
|
7609
7940
|
`Unsupported skill resource '${resourcePath}'; supported extensions are .md, .txt, .json, .yaml, and .yml`
|
|
7610
7941
|
);
|
|
7611
7942
|
}
|
|
7612
|
-
const bytes = await
|
|
7943
|
+
const bytes = await readFile3(path);
|
|
7613
7944
|
let content;
|
|
7614
7945
|
try {
|
|
7615
7946
|
content = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
@@ -7634,11 +7965,11 @@ async function removeStaleManifestAssets(workingDir, manifest, desiredPaths) {
|
|
|
7634
7965
|
continue;
|
|
7635
7966
|
}
|
|
7636
7967
|
if (path.endsWith("/SKILL.md") || path.endsWith(`/${GUARDRAIL_FILE}`)) {
|
|
7637
|
-
await rm(
|
|
7968
|
+
await rm(dirname2(path), { recursive: true, force: true });
|
|
7638
7969
|
continue;
|
|
7639
7970
|
}
|
|
7640
7971
|
await rm(path);
|
|
7641
|
-
const parent =
|
|
7972
|
+
const parent = dirname2(path);
|
|
7642
7973
|
if (parent !== workingDir) {
|
|
7643
7974
|
try {
|
|
7644
7975
|
await rmdir(parent);
|
|
@@ -7651,7 +7982,7 @@ async function removeStaleManifestAssets(workingDir, manifest, desiredPaths) {
|
|
|
7651
7982
|
}
|
|
7652
7983
|
}
|
|
7653
7984
|
async function updateManifestDefaultAgent(path, defaultAgentName) {
|
|
7654
|
-
const contents = await
|
|
7985
|
+
const contents = await readFile3(path, "utf8");
|
|
7655
7986
|
const linePattern = /^defaultAgentName:[^\r\n]*(?:\r?\n|$)/mu;
|
|
7656
7987
|
let updated;
|
|
7657
7988
|
if (defaultAgentName === void 0) {
|
|
@@ -7669,7 +8000,37 @@ ${line}
|
|
|
7669
8000
|
`;
|
|
7670
8001
|
}
|
|
7671
8002
|
}
|
|
7672
|
-
await
|
|
8003
|
+
await writeFile2(path, updated, "utf8");
|
|
8004
|
+
}
|
|
8005
|
+
async function bindManifestWorkspace(path, instanceKey, workspaceId) {
|
|
8006
|
+
const contents = await readFile3(path, "utf8");
|
|
8007
|
+
const lines = contents.split("\n");
|
|
8008
|
+
const keyLine = lines.findIndex((line) => {
|
|
8009
|
+
const match = /^(\s+)(["']?)([^"':]+)\2:\s*(?:#.*)?$/u.exec(line.trimEnd());
|
|
8010
|
+
return match?.[3] === instanceKey;
|
|
8011
|
+
});
|
|
8012
|
+
const keyIndent = keyLine < 0 ? -1 : indentOf(lines[keyLine] ?? "");
|
|
8013
|
+
let urlLine = -1;
|
|
8014
|
+
for (let index = keyLine + 1; keyLine >= 0 && index < lines.length; index += 1) {
|
|
8015
|
+
const line = lines[index] ?? "";
|
|
8016
|
+
if (line.trim() === "" || line.trim().startsWith("#")) continue;
|
|
8017
|
+
if (indentOf(line) <= keyIndent) break;
|
|
8018
|
+
if (/^\s+url:/u.test(line)) {
|
|
8019
|
+
urlLine = index;
|
|
8020
|
+
break;
|
|
8021
|
+
}
|
|
8022
|
+
}
|
|
8023
|
+
if (urlLine < 0) {
|
|
8024
|
+
throw new Error(
|
|
8025
|
+
`Cannot bind instance '${instanceKey}' in ${MANIFEST_FILENAME}: add 'workspace: ${workspaceId}' to that instance yourself, beside its url.`
|
|
8026
|
+
);
|
|
8027
|
+
}
|
|
8028
|
+
const entry = yaml3.dump({ workspace: workspaceId }, { lineWidth: -1 }).trimEnd();
|
|
8029
|
+
lines.splice(urlLine + 1, 0, `${" ".repeat(indentOf(lines[urlLine] ?? ""))}${entry}`);
|
|
8030
|
+
await writeFile2(path, lines.join("\n"), "utf8");
|
|
8031
|
+
}
|
|
8032
|
+
function indentOf(line) {
|
|
8033
|
+
return line.length - line.trimStart().length;
|
|
7673
8034
|
}
|
|
7674
8035
|
async function walkFiles(root, directory, output) {
|
|
7675
8036
|
let entries;
|
|
@@ -7682,7 +8043,7 @@ async function walkFiles(root, directory, output) {
|
|
|
7682
8043
|
throw error;
|
|
7683
8044
|
}
|
|
7684
8045
|
for (const entry of entries) {
|
|
7685
|
-
const path =
|
|
8046
|
+
const path = resolve2(directory, entry.name);
|
|
7686
8047
|
if (entry.isDirectory()) {
|
|
7687
8048
|
if (directory === root && [".git", "node_modules"].includes(entry.name)) {
|
|
7688
8049
|
continue;
|
|
@@ -7697,7 +8058,7 @@ function globSearchRoot(root, pattern) {
|
|
|
7697
8058
|
const wildcardIndex = pattern.search(/[?*]/u);
|
|
7698
8059
|
const fixedPart = wildcardIndex === -1 ? pattern : pattern.slice(0, wildcardIndex);
|
|
7699
8060
|
const slashIndex = fixedPart.lastIndexOf("/");
|
|
7700
|
-
return
|
|
8061
|
+
return resolve2(root, slashIndex === -1 ? "." : fixedPart.slice(0, slashIndex));
|
|
7701
8062
|
}
|
|
7702
8063
|
function globPatternToRegExp(pattern) {
|
|
7703
8064
|
let source = "^";
|
|
@@ -7799,9 +8160,9 @@ async function readAssetSource(path, representation) {
|
|
|
7799
8160
|
return representation === "files" ? { files: await readFileSet(path) } : { config: await readDefinition(path) };
|
|
7800
8161
|
}
|
|
7801
8162
|
async function readDefinition(path) {
|
|
7802
|
-
const folder = (await
|
|
7803
|
-
const guardrailFolder = folder ? path : basename(path) === GUARDRAIL_FILE ?
|
|
7804
|
-
if (guardrailFolder !== void 0 && await
|
|
8163
|
+
const folder = (await stat3(path)).isDirectory();
|
|
8164
|
+
const guardrailFolder = folder ? path : basename(path) === GUARDRAIL_FILE ? dirname3(path) : void 0;
|
|
8165
|
+
if (guardrailFolder !== void 0 && await stat3(join3(guardrailFolder, GUARDRAIL_FILE)).then(
|
|
7805
8166
|
(found) => found.isFile(),
|
|
7806
8167
|
() => false
|
|
7807
8168
|
)) {
|
|
@@ -7811,8 +8172,8 @@ async function readDefinition(path) {
|
|
|
7811
8172
|
);
|
|
7812
8173
|
});
|
|
7813
8174
|
}
|
|
7814
|
-
const file = folder ?
|
|
7815
|
-
const { size } = await
|
|
8175
|
+
const file = folder ? join3(path, SKILL_FILE) : path;
|
|
8176
|
+
const { size } = await stat3(file).catch(() => {
|
|
7816
8177
|
throw new Error(
|
|
7817
8178
|
`'${path}' is a folder without a ${SKILL_FILE}. Name the YAML or JSON file of the definition.`
|
|
7818
8179
|
);
|
|
@@ -7822,7 +8183,7 @@ async function readDefinition(path) {
|
|
|
7822
8183
|
`'${file}' has ${size} bytes, and a definition holds at most ${ASSET_SYNC_ITEM_MAX_BYTES}.`
|
|
7823
8184
|
);
|
|
7824
8185
|
}
|
|
7825
|
-
const contents = await
|
|
8186
|
+
const contents = await readFile4(file, "utf8");
|
|
7826
8187
|
const definition = await parseDefinition(file, contents).catch((error) => {
|
|
7827
8188
|
throw new Error(
|
|
7828
8189
|
`'${file}' cannot be read: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7843,7 +8204,7 @@ function definitionOfShown(value) {
|
|
|
7843
8204
|
async function parseDefinition(file, contents) {
|
|
7844
8205
|
if (basename(file) === SKILL_FILE) {
|
|
7845
8206
|
const skill = parseSkillMarkdown(contents, file, file.split(sep2).join("/"));
|
|
7846
|
-
const resources = await readSkillResources(
|
|
8207
|
+
const resources = await readSkillResources(resolve3(file));
|
|
7847
8208
|
return resources.length === 0 || typeof skill !== "object" ? skill : { ...skill, resources };
|
|
7848
8209
|
}
|
|
7849
8210
|
switch (extname(file).toLowerCase()) {
|
|
@@ -7859,7 +8220,7 @@ async function parseDefinition(file, contents) {
|
|
|
7859
8220
|
}
|
|
7860
8221
|
}
|
|
7861
8222
|
async function readFileSet(directory) {
|
|
7862
|
-
if (!(await
|
|
8223
|
+
if (!(await stat3(directory)).isDirectory()) {
|
|
7863
8224
|
throw new Error(
|
|
7864
8225
|
`'${directory}' is not a folder. An asset of files is written from its folder.`
|
|
7865
8226
|
);
|
|
@@ -7875,7 +8236,7 @@ async function readFileSet(directory) {
|
|
|
7875
8236
|
);
|
|
7876
8237
|
}
|
|
7877
8238
|
let total = 0;
|
|
7878
|
-
for (const path of paths) total += (await
|
|
8239
|
+
for (const path of paths) total += (await stat3(path)).size;
|
|
7879
8240
|
if (total > ASSET_PUT_FILES_MAX_BYTES) {
|
|
7880
8241
|
throw new Error(
|
|
7881
8242
|
`'${directory}' holds ${total} bytes, and one write carries at most ${ASSET_PUT_FILES_MAX_BYTES}.`
|
|
@@ -7883,7 +8244,7 @@ async function readFileSet(directory) {
|
|
|
7883
8244
|
}
|
|
7884
8245
|
const files = [];
|
|
7885
8246
|
for (const path of paths.sort()) {
|
|
7886
|
-
const bytes = await
|
|
8247
|
+
const bytes = await readFile4(path);
|
|
7887
8248
|
const relativePath = relative2(directory, path).split(sep2).join("/");
|
|
7888
8249
|
const text = utf8Text(bytes);
|
|
7889
8250
|
files.push(
|
|
@@ -7913,6 +8274,10 @@ function formatContext(context) {
|
|
|
7913
8274
|
...context.namespaces.map(
|
|
7914
8275
|
(namespace) => ` ${namespace.prefix} ${namespace.displayName}: ${list(namespace.actions)}`
|
|
7915
8276
|
),
|
|
8277
|
+
context.workspaces.length === 0 ? "Workspaces: none" : "Workspaces:",
|
|
8278
|
+
...context.workspaces.map(
|
|
8279
|
+
(workspace) => ` ${workspace.id} ${workspace.name}: ${list(workspace.actions)}`
|
|
8280
|
+
),
|
|
7916
8281
|
`Kinds: ${context.kinds.map((kind) => kind.kind).join(", ")}`
|
|
7917
8282
|
].join("\n");
|
|
7918
8283
|
}
|
|
@@ -8052,82 +8417,6 @@ function isRecord4(value) {
|
|
|
8052
8417
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8053
8418
|
}
|
|
8054
8419
|
|
|
8055
|
-
// src/credentials.ts
|
|
8056
|
-
import { chmod, mkdir as mkdir2, readFile as readFile4, rename as rename2, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
8057
|
-
import { homedir } from "os";
|
|
8058
|
-
import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
|
|
8059
|
-
var CREDENTIALS_FILENAME = "credentials.json";
|
|
8060
|
-
function credentialsPath(env) {
|
|
8061
|
-
const folder = env.CATALYST_CONFIG_HOME ?? join3(env.XDG_CONFIG_HOME ?? join3(homedir(), ".config"), "catalyst");
|
|
8062
|
-
return resolve3(folder, CREDENTIALS_FILENAME);
|
|
8063
|
-
}
|
|
8064
|
-
function credentialOrigin(url) {
|
|
8065
|
-
return new URL(url).origin;
|
|
8066
|
-
}
|
|
8067
|
-
async function readStoredCredential(env, url) {
|
|
8068
|
-
return (await readCredentialFile(credentialsPath(env))).instances[credentialOrigin(url)];
|
|
8069
|
-
}
|
|
8070
|
-
async function saveCredential(env, url, credential) {
|
|
8071
|
-
const path = credentialsPath(env);
|
|
8072
|
-
const file = await readCredentialFile(path);
|
|
8073
|
-
file.instances[credentialOrigin(url)] = credential;
|
|
8074
|
-
await writeCredentialFile(path, file);
|
|
8075
|
-
return path;
|
|
8076
|
-
}
|
|
8077
|
-
async function removeCredential(env, url) {
|
|
8078
|
-
const path = credentialsPath(env);
|
|
8079
|
-
const file = await readCredentialFile(path);
|
|
8080
|
-
const origin = credentialOrigin(url);
|
|
8081
|
-
if (file.instances[origin] === void 0) return false;
|
|
8082
|
-
delete file.instances[origin];
|
|
8083
|
-
await writeCredentialFile(path, file);
|
|
8084
|
-
return true;
|
|
8085
|
-
}
|
|
8086
|
-
async function readCredentialFile(path) {
|
|
8087
|
-
let contents;
|
|
8088
|
-
try {
|
|
8089
|
-
contents = await readFile4(path, "utf8");
|
|
8090
|
-
} catch (error) {
|
|
8091
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
8092
|
-
return { version: 1, instances: {} };
|
|
8093
|
-
}
|
|
8094
|
-
throw error;
|
|
8095
|
-
}
|
|
8096
|
-
if (process.platform !== "win32" && ((await stat3(path)).mode & 63) !== 0) {
|
|
8097
|
-
throw new Error(
|
|
8098
|
-
`${path} can be read by other users of this machine. Run 'chmod 600 ${path}', or remove it and run 'catalyst login' again.`
|
|
8099
|
-
);
|
|
8100
|
-
}
|
|
8101
|
-
const parsed = JSON.parse(contents);
|
|
8102
|
-
if (!isCredentialFile(parsed)) {
|
|
8103
|
-
throw new Error(`${path} is not a credentials file of this CLI. Remove it and log in again.`);
|
|
8104
|
-
}
|
|
8105
|
-
return parsed;
|
|
8106
|
-
}
|
|
8107
|
-
async function writeCredentialFile(path, file) {
|
|
8108
|
-
await mkdir2(dirname3(path), { recursive: true, mode: 448 });
|
|
8109
|
-
const temporary = `${path}.${process.pid}.tmp`;
|
|
8110
|
-
await writeFile2(temporary, `${JSON.stringify(file, null, 2)}
|
|
8111
|
-
`, { mode: 384 });
|
|
8112
|
-
await chmod(temporary, 384);
|
|
8113
|
-
await rename2(temporary, path);
|
|
8114
|
-
}
|
|
8115
|
-
function isCredentialFile(value) {
|
|
8116
|
-
if (typeof value !== "object" || value === null) return false;
|
|
8117
|
-
if (!("version" in value) || value.version !== 1) return false;
|
|
8118
|
-
if (!("instances" in value) || typeof value.instances !== "object" || value.instances === null) {
|
|
8119
|
-
return false;
|
|
8120
|
-
}
|
|
8121
|
-
return Object.values(value.instances).every(
|
|
8122
|
-
(entry) => typeof entry === "object" && entry !== null && "apiKey" in entry && typeof entry.apiKey === "string" && "keyId" in entry && typeof entry.keyId === "string" && "name" in entry && typeof entry.name === "string" && "expiresAt" in entry && typeof entry.expiresAt === "string" && "savedAt" in entry && typeof entry.savedAt === "string"
|
|
8123
|
-
);
|
|
8124
|
-
}
|
|
8125
|
-
|
|
8126
|
-
// src/env.ts
|
|
8127
|
-
function readConfigCliEnv(env) {
|
|
8128
|
-
return env ?? process.env;
|
|
8129
|
-
}
|
|
8130
|
-
|
|
8131
8420
|
// src/command-support.ts
|
|
8132
8421
|
import { createHash } from "crypto";
|
|
8133
8422
|
import { mkdir as mkdir3, mkdtemp, rename as rename3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -8264,7 +8553,16 @@ function workingCopyKinds(manifest) {
|
|
|
8264
8553
|
}
|
|
8265
8554
|
var PULL_CONCURRENCY = 8;
|
|
8266
8555
|
async function readInstanceOverview(api, instance) {
|
|
8267
|
-
return instance
|
|
8556
|
+
return holdsWholeInstance(instance) ? api.getOverview() : {};
|
|
8557
|
+
}
|
|
8558
|
+
function batchScope(instance) {
|
|
8559
|
+
return {
|
|
8560
|
+
...instance.namespace === void 0 ? {} : { namespace: instance.namespace },
|
|
8561
|
+
...instance.workspace === void 0 ? {} : { workspaceId: instance.workspace }
|
|
8562
|
+
};
|
|
8563
|
+
}
|
|
8564
|
+
function holdsWholeInstance(instance) {
|
|
8565
|
+
return instance.namespace === void 0 && instance.workspace === void 0;
|
|
8268
8566
|
}
|
|
8269
8567
|
async function listRemoteAssets(api, kinds) {
|
|
8270
8568
|
const assets = /* @__PURE__ */ new Map();
|
|
@@ -8519,7 +8817,7 @@ function isNodeError2(error) {
|
|
|
8519
8817
|
function commandEnv(options) {
|
|
8520
8818
|
return readConfigCliEnv(options.env);
|
|
8521
8819
|
}
|
|
8522
|
-
async function connectApi(url, options) {
|
|
8820
|
+
async function connectApi(url, options, bound = {}) {
|
|
8523
8821
|
const apiKey = commandEnv(options).CATALYST_API_KEY || (await readStoredCredential(commandEnv(options), url))?.apiKey;
|
|
8524
8822
|
if (!apiKey) {
|
|
8525
8823
|
throw new Error(
|
|
@@ -8530,7 +8828,8 @@ async function connectApi(url, options) {
|
|
|
8530
8828
|
baseUrl: url,
|
|
8531
8829
|
apiKey,
|
|
8532
8830
|
...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
|
|
8533
|
-
...options.sleep === void 0 ? {} : { sleep: options.sleep }
|
|
8831
|
+
...options.sleep === void 0 ? {} : { sleep: options.sleep },
|
|
8832
|
+
...bound.workspace === void 0 ? {} : { workspaceId: bound.workspace }
|
|
8534
8833
|
});
|
|
8535
8834
|
}
|
|
8536
8835
|
function assetHash(contents) {
|
|
@@ -8724,18 +9023,19 @@ var VERBS = {
|
|
|
8724
9023
|
}
|
|
8725
9024
|
},
|
|
8726
9025
|
validate: {
|
|
8727
|
-
usage: "validate --from <file>",
|
|
8728
|
-
summary: "Check a definition against the instance without writing it (assets.validate)",
|
|
9026
|
+
usage: "validate --from <file-or-folder>",
|
|
9027
|
+
summary: "Check a definition against the instance without writing it (assets.validate); a folder of files is checked against the limits of one write",
|
|
8729
9028
|
named: false,
|
|
8730
9029
|
flags: ["from"],
|
|
8731
9030
|
prepare: (values, _name, cwd) => {
|
|
8732
|
-
const from = fromPath(values, cwd, "--from <file>");
|
|
9031
|
+
const from = fromPath(values, cwd, "--from <file-or-folder>");
|
|
8733
9032
|
return async (session2) => {
|
|
8734
|
-
const
|
|
9033
|
+
const source = await readAssetSource(from, session2.kind.representation);
|
|
9034
|
+
if ("files" in source) return filesRead(session2, from, source.files.length);
|
|
8735
9035
|
const result = await session2.api.read(
|
|
8736
9036
|
(client) => client.assets.validate({
|
|
8737
9037
|
params: { kind: session2.kind.kind },
|
|
8738
|
-
body: { config, ...session2.scope }
|
|
9038
|
+
body: { config: source.config, ...session2.scope }
|
|
8739
9039
|
})
|
|
8740
9040
|
);
|
|
8741
9041
|
writeOut(
|
|
@@ -9103,13 +9403,14 @@ ${assetHelpText}`);
|
|
|
9103
9403
|
throw new UsageError(`Unexpected argument '${extra[0] ?? name}'.`);
|
|
9104
9404
|
}
|
|
9105
9405
|
const call2 = verb.prepare(values, name, options.cwd);
|
|
9106
|
-
const api = await openInstance(options);
|
|
9406
|
+
const { api, workspace } = await openInstance(options);
|
|
9107
9407
|
const context = await api.read((client) => client.platform.context.get());
|
|
9408
|
+
const workspaceId = values.workspace ?? workspace;
|
|
9108
9409
|
return await call2({
|
|
9109
9410
|
api,
|
|
9110
9411
|
kind: findKind(context.kinds, noun),
|
|
9111
9412
|
values,
|
|
9112
|
-
scope:
|
|
9413
|
+
scope: workspaceId === void 0 ? {} : { workspaceId },
|
|
9113
9414
|
options
|
|
9114
9415
|
});
|
|
9115
9416
|
} catch (error) {
|
|
@@ -9125,7 +9426,7 @@ ${assetHelpText}`);
|
|
|
9125
9426
|
}
|
|
9126
9427
|
}
|
|
9127
9428
|
async function runInstanceCommand(command, values, options) {
|
|
9128
|
-
const api = await openInstance(options);
|
|
9429
|
+
const { api } = await openInstance(options);
|
|
9129
9430
|
const json2 = values.json;
|
|
9130
9431
|
const out = (value, text) => writeOut(options, json2 ? JSON.stringify(value, null, 2) : text());
|
|
9131
9432
|
if (command === "references") {
|
|
@@ -9140,11 +9441,8 @@ async function runInstanceCommand(command, values, options) {
|
|
|
9140
9441
|
}
|
|
9141
9442
|
async function openInstance(options) {
|
|
9142
9443
|
const manifest = await readManifestIfPresent(resolveWorkingDir(options));
|
|
9143
|
-
const instance = resolveInstance(
|
|
9144
|
-
|
|
9145
|
-
options.instance
|
|
9146
|
-
);
|
|
9147
|
-
return connectApi(instance.url, options);
|
|
9444
|
+
const instance = await resolveInstance(manifest, options);
|
|
9445
|
+
return { api: await connectApi(instance.url, options), workspace: instance.workspace };
|
|
9148
9446
|
}
|
|
9149
9447
|
function findKind(kinds, noun) {
|
|
9150
9448
|
const wanted = noun.replaceAll("-", "_");
|
|
@@ -9156,6 +9454,18 @@ function findKind(kinds, noun) {
|
|
|
9156
9454
|
}
|
|
9157
9455
|
return kind;
|
|
9158
9456
|
}
|
|
9457
|
+
function filesRead(session2, from, files) {
|
|
9458
|
+
const noun = kindNoun(session2.kind);
|
|
9459
|
+
const draft = session2.kind.lifecycle === "draft_published" ? " as a draft" : "";
|
|
9460
|
+
return print(
|
|
9461
|
+
session2,
|
|
9462
|
+
{ files, checkedByInstance: false },
|
|
9463
|
+
() => [
|
|
9464
|
+
`Read ${files} files from '${from}', within what one write carries.`,
|
|
9465
|
+
`The instance checks the files of a ${session2.kind.kind} as it writes them: 'catalyst ${noun} put <name> --from ${from} --if-revision <n|new>' writes them${draft}.`
|
|
9466
|
+
].join("\n")
|
|
9467
|
+
);
|
|
9468
|
+
}
|
|
9159
9469
|
function settle(session2, outcome, lines) {
|
|
9160
9470
|
if (outcome.status === "pending_approval") {
|
|
9161
9471
|
const text = `Waits for an approval on the instance; nothing has changed yet (Operation Run ${outcome.run.id}).`;
|
|
@@ -9221,9 +9531,12 @@ ${Object.values(VERBS).map((verb) => ` catalyst <kind> ${verb.usage}
|
|
|
9221
9531
|
${verb.summary}`).join("\n")}
|
|
9222
9532
|
|
|
9223
9533
|
Options:
|
|
9224
|
-
--instance <name-or-url> An instance of catalyst.yaml, or its URL
|
|
9534
|
+
--instance <name-or-url> An instance of catalyst.yaml, or its URL. Left out: the
|
|
9535
|
+
defaultInstance of catalyst.yaml; outside a working copy,
|
|
9536
|
+
the one instance a key is saved for
|
|
9225
9537
|
--dir <working-copy-dir> Directory containing catalyst.yaml (default: .)
|
|
9226
|
-
--workspace <id> Address the assets one workspace owns
|
|
9538
|
+
--workspace <id> Address the assets one workspace owns. Left out in a working
|
|
9539
|
+
copy bound to a workspace: that workspace
|
|
9227
9540
|
--json Print the instance's answer as JSON
|
|
9228
9541
|
--help Show this help
|
|
9229
9542
|
|
|
@@ -9338,7 +9651,7 @@ import { resolve as resolve6 } from "path";
|
|
|
9338
9651
|
async function pushConfig(options) {
|
|
9339
9652
|
const workingDir = resolveWorkingDir(options);
|
|
9340
9653
|
const manifest = await readManifest(workingDir);
|
|
9341
|
-
const instance = resolveInstance(manifest, options
|
|
9654
|
+
const instance = await resolveInstance(manifest, options);
|
|
9342
9655
|
const local = await readWorkingCopy(workingDir, manifest);
|
|
9343
9656
|
const selectors = parseOnlySelectors(options.only);
|
|
9344
9657
|
if (options.prune && selectors.length > 0) {
|
|
@@ -9360,7 +9673,7 @@ async function pushConfig(options) {
|
|
|
9360
9673
|
);
|
|
9361
9674
|
}
|
|
9362
9675
|
const baselineAssets = baseline.assets;
|
|
9363
|
-
const api = await connectApi(instance.url, options);
|
|
9676
|
+
const api = await connectApi(instance.url, options, instance);
|
|
9364
9677
|
const listing = await listRemoteAssets(api, workingCopyKinds(manifest));
|
|
9365
9678
|
const touched = assetEntries(bundle).filter(
|
|
9366
9679
|
(asset) => baselineAssets[asset.key]?.hash !== assetHash(asset.contents)
|
|
@@ -9368,7 +9681,7 @@ async function pushConfig(options) {
|
|
|
9368
9681
|
const touchedKeys = new Set(touched.map((asset) => asset.key));
|
|
9369
9682
|
const localKeys = new Set(assetEntries(local).map((asset) => asset.key));
|
|
9370
9683
|
const deleteAssets = options.prune ? [...listing.values()].filter((asset) => !localKeys.has(asset.key)) : [];
|
|
9371
|
-
const changesDefault = instance
|
|
9684
|
+
const changesDefault = holdsWholeInstance(instance) && selectors.length === 0 && (bundle.defaultAgentName ?? null) !== baseline.defaultAgentName;
|
|
9372
9685
|
if (changesDefault && baseline.defaultAgentName === void 0) {
|
|
9373
9686
|
throw new Error(
|
|
9374
9687
|
"No default-agent baseline is recorded. Run 'catalyst config pull' before changing the default agent."
|
|
@@ -9447,7 +9760,7 @@ async function pushConfig(options) {
|
|
|
9447
9760
|
}
|
|
9448
9761
|
if (items.length > 0 || changesDefault) {
|
|
9449
9762
|
const batch = {
|
|
9450
|
-
...instance
|
|
9763
|
+
...batchScope(instance),
|
|
9451
9764
|
items,
|
|
9452
9765
|
...changesDefault ? {
|
|
9453
9766
|
defaultAgentName: bundle.defaultAgentName ?? null,
|
|
@@ -9499,6 +9812,7 @@ async function pushConfig(options) {
|
|
|
9499
9812
|
baseOf,
|
|
9500
9813
|
listing,
|
|
9501
9814
|
afterSync: items.length > 0 || changesDefault,
|
|
9815
|
+
...instance.workspace === void 0 ? {} : { workspaceId: instance.workspace },
|
|
9502
9816
|
options
|
|
9503
9817
|
});
|
|
9504
9818
|
}
|
|
@@ -9538,6 +9852,7 @@ async function syncBatch(api, batch, options) {
|
|
|
9538
9852
|
}
|
|
9539
9853
|
async function writeGuardrails(api, input) {
|
|
9540
9854
|
const { options } = input;
|
|
9855
|
+
const owner = input.workspaceId === void 0 ? {} : { workspaceId: input.workspaceId };
|
|
9541
9856
|
let written = input.afterSync;
|
|
9542
9857
|
const record = (asset, revisions, tombstones = {}) => recordBaseline(
|
|
9543
9858
|
input.workingDir,
|
|
@@ -9555,7 +9870,11 @@ async function writeGuardrails(api, input) {
|
|
|
9555
9870
|
const outcome = await api.change(
|
|
9556
9871
|
(client, idempotencyKey2) => client.assets.put({
|
|
9557
9872
|
params: { kind: asset.kind, name: asset.name },
|
|
9558
|
-
body: {
|
|
9873
|
+
body: {
|
|
9874
|
+
config: { ...asset.config },
|
|
9875
|
+
expectedRevision: base?.revision ?? null,
|
|
9876
|
+
...owner
|
|
9877
|
+
},
|
|
9559
9878
|
idempotencyKey: idempotencyKey2
|
|
9560
9879
|
}),
|
|
9561
9880
|
"It may have been written: run 'catalyst config diff' before pushing again."
|
|
@@ -9580,7 +9899,7 @@ async function writeGuardrails(api, input) {
|
|
|
9580
9899
|
const outcome = await api.change(
|
|
9581
9900
|
(client, idempotencyKey2) => client.assets.delete({
|
|
9582
9901
|
params: { kind: asset.kind, name: asset.name },
|
|
9583
|
-
body: { expectedRevision: base.revision },
|
|
9902
|
+
body: { expectedRevision: base.revision, ...owner },
|
|
9584
9903
|
idempotencyKey: idempotencyKey2
|
|
9585
9904
|
}),
|
|
9586
9905
|
"It may have been applied: run 'catalyst config diff' before pushing again."
|
|
@@ -9819,9 +10138,8 @@ async function runConfigCommand(command, options) {
|
|
|
9819
10138
|
}
|
|
9820
10139
|
async function pullConfig(options) {
|
|
9821
10140
|
const workingDir = resolveWorkingDir(options);
|
|
9822
|
-
const manifest = await
|
|
9823
|
-
const
|
|
9824
|
-
const api = await connectApi(instance.url, options);
|
|
10141
|
+
const { manifest, instance } = await bindWorkspace(workingDir, options);
|
|
10142
|
+
const api = await connectApi(instance.url, options, instance);
|
|
9825
10143
|
const overview = await readInstanceOverview(api, instance);
|
|
9826
10144
|
const listing = await listRemoteAssets(api, workingCopyKinds(manifest));
|
|
9827
10145
|
const selectors = parseOnlySelectors(options.only);
|
|
@@ -9894,7 +10212,7 @@ async function pullConfig(options) {
|
|
|
9894
10212
|
force: true
|
|
9895
10213
|
});
|
|
9896
10214
|
}
|
|
9897
|
-
const fullInstancePull = selectors.length === 0 && instance
|
|
10215
|
+
const fullInstancePull = selectors.length === 0 && holdsWholeInstance(instance);
|
|
9898
10216
|
if (selectors.length === 0) {
|
|
9899
10217
|
await removeStaleManifestAssets(workingDir, manifest, desiredPaths);
|
|
9900
10218
|
}
|
|
@@ -9921,6 +10239,43 @@ async function pullConfig(options) {
|
|
|
9921
10239
|
);
|
|
9922
10240
|
return 0;
|
|
9923
10241
|
}
|
|
10242
|
+
async function bindWorkspace(workingDir, options) {
|
|
10243
|
+
const manifest = await readManifest(workingDir);
|
|
10244
|
+
const instance = await resolveInstance(manifest, options);
|
|
10245
|
+
const wanted = options.workspace;
|
|
10246
|
+
if (wanted === void 0 || instance.workspace === wanted) return { manifest, instance };
|
|
10247
|
+
if (instance.workspace !== void 0) {
|
|
10248
|
+
throw new Error(
|
|
10249
|
+
`This working copy is bound to workspace '${instance.workspace}'. Pull another workspace into a directory of its own.`
|
|
10250
|
+
);
|
|
10251
|
+
}
|
|
10252
|
+
if (instance.namespace !== void 0) {
|
|
10253
|
+
throw new Error(
|
|
10254
|
+
`This working copy belongs to Namespace '${instance.namespace}'. Pull a workspace into a directory of its own.`
|
|
10255
|
+
);
|
|
10256
|
+
}
|
|
10257
|
+
const baseline = (await readStateFile(resolve7(workingDir, STATE_FILENAME))).instances[instance.key];
|
|
10258
|
+
if (baseline !== void 0) {
|
|
10259
|
+
throw new Error(
|
|
10260
|
+
`This working copy holds the instance's own assets of '${instance.key}'. Pull a workspace into a directory of its own.`
|
|
10261
|
+
);
|
|
10262
|
+
}
|
|
10263
|
+
if (manifest.instances[instance.key] === void 0) {
|
|
10264
|
+
throw new Error(
|
|
10265
|
+
`Name the instance in ${MANIFEST_FILENAME} to bind it to a workspace: '${instance.key}' is not one of its instances.`
|
|
10266
|
+
);
|
|
10267
|
+
}
|
|
10268
|
+
await bindManifestWorkspace(resolve7(workingDir, MANIFEST_FILENAME), instance.key, wanted);
|
|
10269
|
+
const bound = await readManifest(workingDir);
|
|
10270
|
+
const boundInstance = await resolveInstance(bound, options);
|
|
10271
|
+
if (boundInstance.workspace !== wanted) {
|
|
10272
|
+
throw new Error(
|
|
10273
|
+
`Could not bind instance '${instance.key}' in ${MANIFEST_FILENAME}: add 'workspace: ${wanted}' to that instance yourself, beside its url.`
|
|
10274
|
+
);
|
|
10275
|
+
}
|
|
10276
|
+
writeOutput(options, `Bound this working copy to workspace '${wanted}'.`);
|
|
10277
|
+
return { manifest: bound, instance: boundInstance };
|
|
10278
|
+
}
|
|
9924
10279
|
function assetPath(workingDir, kind, name) {
|
|
9925
10280
|
switch (kind) {
|
|
9926
10281
|
case "agent":
|
|
@@ -9954,9 +10309,9 @@ function assertPullTargetsMatchManifest(workingDir, manifest, targets) {
|
|
|
9954
10309
|
async function diffConfig(options) {
|
|
9955
10310
|
const workingDir = resolveWorkingDir(options);
|
|
9956
10311
|
const manifest = await readManifest(workingDir);
|
|
9957
|
-
const instance = resolveInstance(manifest, options
|
|
10312
|
+
const instance = await resolveInstance(manifest, options);
|
|
9958
10313
|
const local = await readWorkingCopy(workingDir, manifest);
|
|
9959
|
-
const api = await connectApi(instance.url, options);
|
|
10314
|
+
const api = await connectApi(instance.url, options, instance);
|
|
9960
10315
|
const overview = await readInstanceOverview(api, instance);
|
|
9961
10316
|
const remoteAssets = await listRemoteAssets(api, workingCopyKinds(manifest));
|
|
9962
10317
|
const baseline = (await readStateFile(resolve7(workingDir, STATE_FILENAME))).instances[instance.key];
|
|
@@ -10015,9 +10370,9 @@ async function diffConfig(options) {
|
|
|
10015
10370
|
async function validateConfig(options) {
|
|
10016
10371
|
const workingDir = resolveWorkingDir(options);
|
|
10017
10372
|
const manifest = await readManifest(workingDir);
|
|
10018
|
-
const instance = resolveInstance(manifest, options
|
|
10373
|
+
const instance = await resolveInstance(manifest, options);
|
|
10019
10374
|
const bundle = await readWorkingCopy(workingDir, manifest);
|
|
10020
|
-
const api = await connectApi(instance.url, options);
|
|
10375
|
+
const api = await connectApi(instance.url, options, instance);
|
|
10021
10376
|
const listing = await listRemoteAssets(api, workingCopyKinds(manifest));
|
|
10022
10377
|
const deleted = (await readStateFile(resolve7(workingDir, STATE_FILENAME))).instances[instance.key]?.deleted;
|
|
10023
10378
|
const items = assetEntries(bundle).filter((asset) => asset.kind !== "guardrail").map((asset) => ({
|
|
@@ -10031,6 +10386,7 @@ async function validateConfig(options) {
|
|
|
10031
10386
|
try {
|
|
10032
10387
|
if (items.length > 0 || bundle.defaultAgentName !== void 0) {
|
|
10033
10388
|
await checkSyncOnThisRelease(api, {
|
|
10389
|
+
...batchScope(instance),
|
|
10034
10390
|
items,
|
|
10035
10391
|
...bundle.defaultAgentName === void 0 ? {} : { defaultAgentName: bundle.defaultAgentName }
|
|
10036
10392
|
});
|
|
@@ -10049,9 +10405,9 @@ async function validateConfig(options) {
|
|
|
10049
10405
|
async function listConfig(options) {
|
|
10050
10406
|
const workingDir = resolveWorkingDir(options);
|
|
10051
10407
|
const manifest = await readManifest(workingDir);
|
|
10052
|
-
const instance = resolveInstance(manifest, options
|
|
10408
|
+
const instance = await resolveInstance(manifest, options);
|
|
10053
10409
|
const local = await readWorkingCopy(workingDir, manifest);
|
|
10054
|
-
const api = await connectApi(instance.url, options);
|
|
10410
|
+
const api = await connectApi(instance.url, options, instance);
|
|
10055
10411
|
const version = String((await readInstanceOverview(api, instance)).version ?? "-");
|
|
10056
10412
|
const remote = await listRemoteAssets(api, workingCopyKinds(manifest));
|
|
10057
10413
|
const localKeys = new Set(assetEntries(local).map((asset) => asset.key));
|
|
@@ -10078,8 +10434,8 @@ async function showConfig(options) {
|
|
|
10078
10434
|
}
|
|
10079
10435
|
const workingDir = resolveWorkingDir(options);
|
|
10080
10436
|
const manifest = await readManifest(workingDir);
|
|
10081
|
-
const instance = resolveInstance(manifest, options
|
|
10082
|
-
const api = await connectApi(instance.url, options);
|
|
10437
|
+
const instance = await resolveInstance(manifest, options);
|
|
10438
|
+
const api = await connectApi(instance.url, options, instance);
|
|
10083
10439
|
const remote = await fetchRemoteAsset(api, options.assetKind, options.assetName);
|
|
10084
10440
|
if (!remote) {
|
|
10085
10441
|
throw new Error(`Remote ${options.assetKind} '${options.assetName}' was not found.`);
|
|
@@ -10109,61 +10465,75 @@ import { hostname } from "os";
|
|
|
10109
10465
|
async function loginCommand(options) {
|
|
10110
10466
|
try {
|
|
10111
10467
|
const url = await resolveInstanceUrl(options);
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
});
|
|
10118
|
-
const started = await call(
|
|
10119
|
-
() => client.connect_codes.start({ body: { clientLabel: `catalyst CLI on ${hostname()}` } })
|
|
10120
|
-
);
|
|
10468
|
+
const key = await loginByConnectCode(options, url, `catalyst CLI on ${hostname()}`);
|
|
10469
|
+
if (key === void 0) {
|
|
10470
|
+
writeError(options, "The code expired before it was confirmed. Run 'catalyst login' again.");
|
|
10471
|
+
return 1;
|
|
10472
|
+
}
|
|
10121
10473
|
writeOutput(
|
|
10122
10474
|
options,
|
|
10123
10475
|
[
|
|
10124
|
-
`
|
|
10125
|
-
`
|
|
10126
|
-
`
|
|
10476
|
+
`Logged in to ${credentialOrigin(url)} with the key '${key.name}' (${key.keyPrefix}\u2026).`,
|
|
10477
|
+
`It may: ${key.scopes.join(", ")}, as far as your rights reach, until ${formatTime(key.expiresAt)}.`,
|
|
10478
|
+
`Saved in ${key.path}. See and revoke it at ${apiKeysAddress(url)}.`
|
|
10127
10479
|
].join("\n")
|
|
10128
10480
|
);
|
|
10129
|
-
|
|
10130
|
-
while (Date.now() < deadline) {
|
|
10131
|
-
await sleep(started.intervalSeconds * 1e3);
|
|
10132
|
-
const answer = await call(
|
|
10133
|
-
() => client.connect_codes.redeem({ body: { deviceCode: started.deviceCode } })
|
|
10134
|
-
);
|
|
10135
|
-
if (answer.status === "pending") continue;
|
|
10136
|
-
const path = await saveCredential(readConfigCliEnv(options.env), url, {
|
|
10137
|
-
apiKey: answer.secret,
|
|
10138
|
-
keyId: answer.key.id,
|
|
10139
|
-
name: answer.key.name,
|
|
10140
|
-
expiresAt: answer.key.expiresAt,
|
|
10141
|
-
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10142
|
-
});
|
|
10143
|
-
writeOutput(
|
|
10144
|
-
options,
|
|
10145
|
-
[
|
|
10146
|
-
`Logged in to ${credentialOrigin(url)} with the key '${answer.key.name}' (${answer.key.keyPrefix}\u2026).`,
|
|
10147
|
-
`It may: ${answer.key.scopes.join(", ")}, as far as your rights reach, until ${formatTime(answer.key.expiresAt)}.`,
|
|
10148
|
-
`Saved in ${path}. Revoke it in Settings, API keys.`
|
|
10149
|
-
].join("\n")
|
|
10150
|
-
);
|
|
10151
|
-
return 0;
|
|
10152
|
-
}
|
|
10153
|
-
writeError(options, "The code expired before it was confirmed. Run 'catalyst login' again.");
|
|
10154
|
-
return 1;
|
|
10481
|
+
return 0;
|
|
10155
10482
|
} catch (error) {
|
|
10156
10483
|
writeError(options, formatCommandError(error));
|
|
10157
10484
|
return 1;
|
|
10158
10485
|
}
|
|
10159
10486
|
}
|
|
10487
|
+
async function loginByConnectCode(options, url, clientLabel, openUrl) {
|
|
10488
|
+
assertSafeApiKeyExchangeUrl(url, "receive an API key");
|
|
10489
|
+
const sleep = options.sleep ?? defaultSleep2;
|
|
10490
|
+
const client = createApiClient({
|
|
10491
|
+
baseUrl: url,
|
|
10492
|
+
fetchImpl: instanceFetch(options.fetchImpl)
|
|
10493
|
+
});
|
|
10494
|
+
const started = await call(() => client.connect_codes.start({ body: { clientLabel } }));
|
|
10495
|
+
const address = `${url}${started.confirmPath}`;
|
|
10496
|
+
writeOutput(
|
|
10497
|
+
options,
|
|
10498
|
+
[
|
|
10499
|
+
`Open ${address}`,
|
|
10500
|
+
`and confirm the code ${started.userCode} with the scopes this key may use.`,
|
|
10501
|
+
`Waiting for the confirmation until ${formatTime(started.expiresAt)}\u2026`
|
|
10502
|
+
].join("\n")
|
|
10503
|
+
);
|
|
10504
|
+
openUrl?.(address);
|
|
10505
|
+
const deadline = Date.parse(started.expiresAt);
|
|
10506
|
+
while (Date.now() < deadline) {
|
|
10507
|
+
await sleep(started.intervalSeconds * 1e3);
|
|
10508
|
+
const answer = await call(
|
|
10509
|
+
() => client.connect_codes.redeem({ body: { deviceCode: started.deviceCode } })
|
|
10510
|
+
);
|
|
10511
|
+
if (answer.status === "pending") continue;
|
|
10512
|
+
const path = await saveCredential(readConfigCliEnv(options.env), url, {
|
|
10513
|
+
apiKey: answer.secret,
|
|
10514
|
+
keyId: answer.key.id,
|
|
10515
|
+
name: answer.key.name,
|
|
10516
|
+
scopes: answer.key.scopes,
|
|
10517
|
+
expiresAt: answer.key.expiresAt,
|
|
10518
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10519
|
+
});
|
|
10520
|
+
return {
|
|
10521
|
+
name: answer.key.name,
|
|
10522
|
+
keyPrefix: answer.key.keyPrefix,
|
|
10523
|
+
scopes: answer.key.scopes,
|
|
10524
|
+
expiresAt: answer.key.expiresAt,
|
|
10525
|
+
path
|
|
10526
|
+
};
|
|
10527
|
+
}
|
|
10528
|
+
return void 0;
|
|
10529
|
+
}
|
|
10160
10530
|
async function logoutCommand(options) {
|
|
10161
10531
|
try {
|
|
10162
10532
|
const url = await resolveInstanceUrl(options);
|
|
10163
10533
|
const removed = await removeCredential(readConfigCliEnv(options.env), url);
|
|
10164
10534
|
writeOutput(
|
|
10165
10535
|
options,
|
|
10166
|
-
removed ? `Removed the saved key of ${credentialOrigin(url)}. It stays valid until you revoke it
|
|
10536
|
+
removed ? `Removed the saved key of ${credentialOrigin(url)}. It stays valid until you revoke it at ${apiKeysAddress(url)}.` : `No key is saved for ${credentialOrigin(url)}.`
|
|
10167
10537
|
);
|
|
10168
10538
|
return 0;
|
|
10169
10539
|
} catch (error) {
|
|
@@ -10176,16 +10546,11 @@ async function resolveInstanceUrl(options) {
|
|
|
10176
10546
|
if (requested !== void 0 && /^https?:\/\//u.test(requested)) {
|
|
10177
10547
|
return requested.replace(/\/+$/u, "");
|
|
10178
10548
|
}
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
`No ${MANIFEST_FILENAME} here to name the instance. Pass --instance <https-url>.`
|
|
10185
|
-
);
|
|
10186
|
-
}
|
|
10187
|
-
throw error;
|
|
10188
|
-
}
|
|
10549
|
+
const manifest = await readManifestIfPresent(resolveWorkingDir(options));
|
|
10550
|
+
return (await resolveInstance(manifest, options)).url;
|
|
10551
|
+
}
|
|
10552
|
+
function apiKeysAddress(url) {
|
|
10553
|
+
return `${url}/settings/you/api-keys`;
|
|
10189
10554
|
}
|
|
10190
10555
|
async function call(request) {
|
|
10191
10556
|
try {
|
|
@@ -10201,17 +10566,185 @@ function defaultSleep2(milliseconds) {
|
|
|
10201
10566
|
return new Promise((resolveSleep) => setTimeout(resolveSleep, milliseconds));
|
|
10202
10567
|
}
|
|
10203
10568
|
|
|
10569
|
+
// src/setup.ts
|
|
10570
|
+
import { spawn } from "child_process";
|
|
10571
|
+
import { createHash as createHash2 } from "crypto";
|
|
10572
|
+
import { lstat, mkdir as mkdir5, readFile as readFile6, rename as rename4, rm as rm4, writeFile as writeFile5 } from "fs/promises";
|
|
10573
|
+
import { homedir as homedir2, hostname as hostname2 } from "os";
|
|
10574
|
+
import { dirname as dirname6, join as join4 } from "path";
|
|
10575
|
+
var LOCAL_AGENT_TOOLS = {
|
|
10576
|
+
"claude-code": { label: "Claude Code", skillsFolder: [".claude", "skills"] },
|
|
10577
|
+
// Codex reads `~/.agents/skills`, the shared place for tools other than Claude Code.
|
|
10578
|
+
codex: { label: "Codex", skillsFolder: [".agents", "skills"] },
|
|
10579
|
+
other: { label: "Local agent", skillsFolder: [".agents", "skills"] }
|
|
10580
|
+
};
|
|
10581
|
+
function isLocalAgentTool(value) {
|
|
10582
|
+
return value !== void 0 && Object.hasOwn(LOCAL_AGENT_TOOLS, value);
|
|
10583
|
+
}
|
|
10584
|
+
var SETUP_USAGE = "Usage: catalyst setup <claude-code|codex|other> --instance <url> [--force]";
|
|
10585
|
+
var SHIPPED_SKILL = new URL("../skills/catalyst/SKILL.md", import.meta.url);
|
|
10586
|
+
var MARK = /\n<!-- Written by catalyst setup \(@work-shape\/catalyst-cli [^,]+, sha256 ([0-9a-f]{64})\)\. -->\n$/u;
|
|
10587
|
+
function sha256(text) {
|
|
10588
|
+
return createHash2("sha256").update(text).digest("hex");
|
|
10589
|
+
}
|
|
10590
|
+
async function renderCatalystSkill() {
|
|
10591
|
+
const body = await readFile6(SHIPPED_SKILL, "utf8");
|
|
10592
|
+
return `${body}
|
|
10593
|
+
<!-- Written by catalyst setup (@work-shape/catalyst-cli ${RELEASE_VERSION}, sha256 ${sha256(body)}). -->
|
|
10594
|
+
`;
|
|
10595
|
+
}
|
|
10596
|
+
function writtenBySetup(contents) {
|
|
10597
|
+
const mark2 = MARK.exec(contents);
|
|
10598
|
+
return mark2 !== null && sha256(contents.slice(0, mark2.index)) === mark2[1];
|
|
10599
|
+
}
|
|
10600
|
+
function catalystSkillPath(tool, env) {
|
|
10601
|
+
const folder = tool === "claude-code" && env.CLAUDE_CONFIG_DIR ? join4(env.CLAUDE_CONFIG_DIR, "skills") : join4(env.HOME || homedir2(), ...LOCAL_AGENT_TOOLS[tool].skillsFolder);
|
|
10602
|
+
return join4(folder, "catalyst", "SKILL.md");
|
|
10603
|
+
}
|
|
10604
|
+
async function setupCommand(options) {
|
|
10605
|
+
try {
|
|
10606
|
+
const url = await resolveInstanceUrl(options);
|
|
10607
|
+
const tool = LOCAL_AGENT_TOOLS[options.tool];
|
|
10608
|
+
const saved = await workingSavedKey(options, url);
|
|
10609
|
+
let keyLine;
|
|
10610
|
+
if (saved) {
|
|
10611
|
+
keyLine = `Kept the saved key '${saved.name}'${scopesOf(saved.scopes)}, valid until ${formatTime(saved.expiresAt)}.`;
|
|
10612
|
+
} else {
|
|
10613
|
+
const key = await loginByConnectCode(
|
|
10614
|
+
options,
|
|
10615
|
+
url,
|
|
10616
|
+
`${tool.label} on ${hostname2()}`,
|
|
10617
|
+
options.openUrl ?? openInBrowser(url)
|
|
10618
|
+
);
|
|
10619
|
+
if (key === void 0) {
|
|
10620
|
+
writeError(
|
|
10621
|
+
options,
|
|
10622
|
+
`The code expired before it was confirmed. Run 'catalyst setup ${options.tool} --instance ${url}' again.`
|
|
10623
|
+
);
|
|
10624
|
+
return 1;
|
|
10625
|
+
}
|
|
10626
|
+
keyLine = `Logged in with the key '${key.name}'${scopesOf(key.scopes)}, valid until ${formatTime(key.expiresAt)}.`;
|
|
10627
|
+
}
|
|
10628
|
+
const path = catalystSkillPath(options.tool, readConfigCliEnv(options.env));
|
|
10629
|
+
const skill = await renderCatalystSkill();
|
|
10630
|
+
const existing = await readSkillFile(path);
|
|
10631
|
+
const lines = [`Instance: ${credentialOrigin(url)}`, keyLine];
|
|
10632
|
+
if (existing === skill) {
|
|
10633
|
+
lines.push(`The Catalyst skill at ${path} is current.`);
|
|
10634
|
+
} else if (existing === void 0 || existing !== NOT_A_FILE && writtenBySetup(existing)) {
|
|
10635
|
+
await replaceFile(path, skill);
|
|
10636
|
+
lines.push(`Wrote the Catalyst skill to ${path}.`);
|
|
10637
|
+
} else if (options.force) {
|
|
10638
|
+
const backup = await freeBackupPath(path);
|
|
10639
|
+
await rename4(path, backup);
|
|
10640
|
+
await replaceFile(path, skill);
|
|
10641
|
+
lines.push(`Wrote the Catalyst skill to ${path}; the file that was there is ${backup}.`);
|
|
10642
|
+
} else {
|
|
10643
|
+
writeOutput(options, lines.join("\n"));
|
|
10644
|
+
writeError(
|
|
10645
|
+
options,
|
|
10646
|
+
`Kept ${path}: it is not the skill this CLI writes. Run the command again with --force to replace it; the file that is there is then kept as ${await freeBackupPath(path)}.`
|
|
10647
|
+
);
|
|
10648
|
+
return 1;
|
|
10649
|
+
}
|
|
10650
|
+
if (options.tool === "other") {
|
|
10651
|
+
lines.push("Point your tool at that file, or copy it to where your tool reads skills.");
|
|
10652
|
+
}
|
|
10653
|
+
lines.push(`See and revoke the key at ${apiKeysAddress(url)}.`);
|
|
10654
|
+
writeOutput(options, lines.join("\n"));
|
|
10655
|
+
return 0;
|
|
10656
|
+
} catch (error) {
|
|
10657
|
+
writeError(options, formatCommandError(error));
|
|
10658
|
+
return 1;
|
|
10659
|
+
}
|
|
10660
|
+
}
|
|
10661
|
+
var NOT_A_FILE = /* @__PURE__ */ Symbol("not a file");
|
|
10662
|
+
async function readSkillFile(path) {
|
|
10663
|
+
const entry = await lstat(path).catch((error) => {
|
|
10664
|
+
if (isMissing(error)) return void 0;
|
|
10665
|
+
throw error;
|
|
10666
|
+
});
|
|
10667
|
+
if (entry === void 0) return void 0;
|
|
10668
|
+
return entry.isFile() ? readFile6(path, "utf8") : NOT_A_FILE;
|
|
10669
|
+
}
|
|
10670
|
+
async function replaceFile(path, contents) {
|
|
10671
|
+
await mkdir5(dirname6(path), { recursive: true });
|
|
10672
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
10673
|
+
await writeFile5(temporary, contents, { flag: "wx" });
|
|
10674
|
+
try {
|
|
10675
|
+
await rename4(temporary, path);
|
|
10676
|
+
} catch (error) {
|
|
10677
|
+
await rm4(temporary, { force: true });
|
|
10678
|
+
throw error;
|
|
10679
|
+
}
|
|
10680
|
+
}
|
|
10681
|
+
async function freeBackupPath(path) {
|
|
10682
|
+
for (let number = 0; ; number += 1) {
|
|
10683
|
+
const candidate = number === 0 ? `${path}.bak` : `${path}.bak.${number}`;
|
|
10684
|
+
const taken = await lstat(candidate).then(
|
|
10685
|
+
() => true,
|
|
10686
|
+
(error) => {
|
|
10687
|
+
if (isMissing(error)) return false;
|
|
10688
|
+
throw error;
|
|
10689
|
+
}
|
|
10690
|
+
);
|
|
10691
|
+
if (!taken) return candidate;
|
|
10692
|
+
}
|
|
10693
|
+
}
|
|
10694
|
+
function isMissing(error) {
|
|
10695
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
10696
|
+
}
|
|
10697
|
+
async function workingSavedKey(options, url) {
|
|
10698
|
+
const saved = await readStoredCredential(readConfigCliEnv(options.env), url);
|
|
10699
|
+
if (saved === void 0 || Date.parse(saved.expiresAt) <= Date.now()) return void 0;
|
|
10700
|
+
try {
|
|
10701
|
+
const api = await createConfigApi({
|
|
10702
|
+
baseUrl: url,
|
|
10703
|
+
apiKey: saved.apiKey,
|
|
10704
|
+
...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
|
|
10705
|
+
...options.sleep === void 0 ? {} : { sleep: options.sleep }
|
|
10706
|
+
});
|
|
10707
|
+
await api.read((client) => client.platform.context.get());
|
|
10708
|
+
return saved;
|
|
10709
|
+
} catch (error) {
|
|
10710
|
+
if (error instanceof ApiKeyExchangeError && (error.status === 401 || error.status === 403)) {
|
|
10711
|
+
return void 0;
|
|
10712
|
+
}
|
|
10713
|
+
throw error;
|
|
10714
|
+
}
|
|
10715
|
+
}
|
|
10716
|
+
function scopesOf(scopes) {
|
|
10717
|
+
return scopes === void 0 ? "" : ` (${scopes.join(", ")})`;
|
|
10718
|
+
}
|
|
10719
|
+
function openInBrowser(url) {
|
|
10720
|
+
return (address) => {
|
|
10721
|
+
if (new URL(address).origin !== credentialOrigin(url)) return;
|
|
10722
|
+
const [command, args] = process.platform === "darwin" ? ["open", [address]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", address]] : ["xdg-open", [address]];
|
|
10723
|
+
try {
|
|
10724
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
10725
|
+
child.on("error", () => void 0);
|
|
10726
|
+
child.unref();
|
|
10727
|
+
} catch {
|
|
10728
|
+
}
|
|
10729
|
+
};
|
|
10730
|
+
}
|
|
10731
|
+
|
|
10204
10732
|
// src/index.ts
|
|
10205
10733
|
var helpText = `Usage: catalyst config <command> [options]
|
|
10206
10734
|
catalyst login --instance <name-or-url>
|
|
10207
10735
|
catalyst logout --instance <name-or-url>
|
|
10736
|
+
catalyst setup <claude-code|codex|other> --instance <url> [--force]
|
|
10208
10737
|
|
|
10209
10738
|
Sync local config assets with a live Catalyst instance.
|
|
10210
10739
|
|
|
10211
10740
|
Login:
|
|
10212
|
-
login Show a connect code, wait until you confirm it
|
|
10213
|
-
save the personal API key it issues, readable by
|
|
10214
|
-
|
|
10741
|
+
login Show a connect code, wait until you confirm it on the instance at
|
|
10742
|
+
/settings/you/api-keys, and save the personal API key it issues, readable by
|
|
10743
|
+
you alone
|
|
10744
|
+
logout Forget the saved key of an instance; revoke it at /settings/you/api-keys
|
|
10745
|
+
setup Connect a local agent: log in as 'login' does unless a working key is saved,
|
|
10746
|
+
then write the Catalyst skill for Claude Code, Codex or another tool. --force
|
|
10747
|
+
replaces a different skill file there and keeps it as SKILL.md.bak
|
|
10215
10748
|
|
|
10216
10749
|
Commands:
|
|
10217
10750
|
pull Sync remote config assets into the local working copy
|
|
@@ -10222,13 +10755,18 @@ Commands:
|
|
|
10222
10755
|
show Print a remote asset: show <agent|skill|guardrail> <name>
|
|
10223
10756
|
|
|
10224
10757
|
Options:
|
|
10225
|
-
--instance <name-or-url> Manifest instance name or direct URL
|
|
10758
|
+
--instance <name-or-url> Manifest instance name or direct URL. Left out: the
|
|
10759
|
+
manifest's defaultInstance; outside a working copy, the
|
|
10760
|
+
one instance a key is saved for
|
|
10226
10761
|
--dir <working-copy-dir> Directory containing catalyst.yaml (default: .)
|
|
10227
10762
|
--prune Mirror on push, deleting the remote-only assets the last pull
|
|
10228
10763
|
saw
|
|
10229
10764
|
--only <agent:name|skill:name|guardrail:name>
|
|
10230
10765
|
Sync selected assets; repeatable on push/pull
|
|
10766
|
+
--workspace <id> On pull: bind the working copy to the workspace and hold
|
|
10767
|
+
what it owns
|
|
10231
10768
|
--help Show this help
|
|
10769
|
+
--version Print the version of this CLI
|
|
10232
10770
|
|
|
10233
10771
|
Environment:
|
|
10234
10772
|
CATALYST_API_KEY API key exchanged for a short-lived access token. It wins over
|
|
@@ -10236,10 +10774,16 @@ Environment:
|
|
|
10236
10774
|
CATALYST_CONFIG_HOME Folder of the saved keys (default: ~/.config/catalyst)
|
|
10237
10775
|
|
|
10238
10776
|
A working copy that belongs to one Namespace names it beside the instance's url in
|
|
10239
|
-
catalyst.yaml, as 'namespace:
|
|
10777
|
+
catalyst.yaml, as 'namespace: tax-'. Its push names that Namespace, as the instance asks
|
|
10240
10778
|
of everyone but the holder of the release right; its pull, diff and list read only the
|
|
10241
10779
|
assets the key may read, never the instance's version or default agent.
|
|
10242
10780
|
|
|
10781
|
+
A working copy bound to a workspace names it the same way, as 'workspace: <id>', which the
|
|
10782
|
+
first 'catalyst config pull --workspace <id>' writes. It holds what that workspace owns, and
|
|
10783
|
+
everything its push creates is owned by that workspace, guardrails included. Its push is never
|
|
10784
|
+
a release: a new agent starts hidden until an instance admin offers it. A working copy without
|
|
10785
|
+
either holds the instance's own assets.
|
|
10786
|
+
|
|
10243
10787
|
Guardrails join the working copy where catalyst.yaml lists their globs under 'guardrails',
|
|
10244
10788
|
such as 'guardrails/*/guardrail.yaml'. Each is a folder with its guardrail.yaml and, for a
|
|
10245
10789
|
model_judge check, its instruction in check.md. Guardrails are no part of a release: a push
|
|
@@ -10254,7 +10798,12 @@ async function runCli(argv, runtime = {}) {
|
|
|
10254
10798
|
${helpText}`);
|
|
10255
10799
|
return 0;
|
|
10256
10800
|
}
|
|
10257
|
-
if (argv[0] === "
|
|
10801
|
+
if (argv[0] === "--version") {
|
|
10802
|
+
stdout(`${RELEASE_VERSION}
|
|
10803
|
+
`);
|
|
10804
|
+
return 0;
|
|
10805
|
+
}
|
|
10806
|
+
if (argv[0] === "login" || argv[0] === "logout" || argv[0] === "setup") {
|
|
10258
10807
|
return runLoginCommand(argv[0], argv.slice(1), runtime, stdout, stderr);
|
|
10259
10808
|
}
|
|
10260
10809
|
if (argv[0] !== "config") {
|
|
@@ -10292,6 +10841,7 @@ ${helpText}`);
|
|
|
10292
10841
|
force: { type: "boolean", default: false },
|
|
10293
10842
|
prune: { type: "boolean", default: false },
|
|
10294
10843
|
only: { type: "string", multiple: true },
|
|
10844
|
+
workspace: { type: "string" },
|
|
10295
10845
|
help: { type: "boolean", short: "h", default: false }
|
|
10296
10846
|
},
|
|
10297
10847
|
allowPositionals: true,
|
|
@@ -10313,6 +10863,12 @@ ${helpText}`);
|
|
|
10313
10863
|
stderr("--only is only valid with 'catalyst config push' or 'catalyst config pull'.\n");
|
|
10314
10864
|
return 2;
|
|
10315
10865
|
}
|
|
10866
|
+
if (parsed.values.workspace !== void 0 && command !== "pull") {
|
|
10867
|
+
stderr(
|
|
10868
|
+
"--workspace is only valid with 'catalyst config pull', which binds the working copy.\n"
|
|
10869
|
+
);
|
|
10870
|
+
return 2;
|
|
10871
|
+
}
|
|
10316
10872
|
if (parsed.values.prune && parsed.values.only) {
|
|
10317
10873
|
stderr("--prune cannot be combined with --only.\n");
|
|
10318
10874
|
return 2;
|
|
@@ -10334,6 +10890,7 @@ ${helpText}`);
|
|
|
10334
10890
|
...parsed.values.force ? { force: true } : {},
|
|
10335
10891
|
...parsed.values.prune ? { prune: true } : {},
|
|
10336
10892
|
...parsed.values.only === void 0 ? {} : { only: parsed.values.only },
|
|
10893
|
+
...parsed.values.workspace === void 0 ? {} : { workspace: parsed.values.workspace },
|
|
10337
10894
|
...command === "show" && isShownKind(parsed.positionals[0]) ? { assetKind: parsed.positionals[0], assetName: parsed.positionals[1] } : {},
|
|
10338
10895
|
...runtime.fetchImpl === void 0 ? {} : { fetchImpl: runtime.fetchImpl },
|
|
10339
10896
|
...runtime.env === void 0 ? {} : { env: runtime.env },
|
|
@@ -10355,8 +10912,10 @@ async function runLoginCommand(command, args, runtime, stdout, stderr) {
|
|
|
10355
10912
|
options: {
|
|
10356
10913
|
instance: { type: "string" },
|
|
10357
10914
|
dir: { type: "string" },
|
|
10915
|
+
force: { type: "boolean", default: false },
|
|
10358
10916
|
help: { type: "boolean", short: "h", default: false }
|
|
10359
10917
|
},
|
|
10918
|
+
allowPositionals: command === "setup",
|
|
10360
10919
|
strict: true
|
|
10361
10920
|
});
|
|
10362
10921
|
} catch (error) {
|
|
@@ -10368,6 +10927,11 @@ async function runLoginCommand(command, args, runtime, stdout, stderr) {
|
|
|
10368
10927
|
stdout(helpText);
|
|
10369
10928
|
return 0;
|
|
10370
10929
|
}
|
|
10930
|
+
if (parsed.values.force && command !== "setup") {
|
|
10931
|
+
stderr(`--force is not an option of 'catalyst ${command}'.
|
|
10932
|
+
`);
|
|
10933
|
+
return 2;
|
|
10934
|
+
}
|
|
10371
10935
|
const options = {
|
|
10372
10936
|
cwd: runtime.cwd ?? process.cwd(),
|
|
10373
10937
|
...parsed.values.dir === void 0 ? {} : { dir: parsed.values.dir },
|
|
@@ -10378,6 +10942,20 @@ async function runLoginCommand(command, args, runtime, stdout, stderr) {
|
|
|
10378
10942
|
stdout,
|
|
10379
10943
|
stderr
|
|
10380
10944
|
};
|
|
10945
|
+
if (command === "setup") {
|
|
10946
|
+
const [tool, ...extra] = parsed.positionals;
|
|
10947
|
+
if (!isLocalAgentTool(tool) || extra.length > 0 || parsed.values.instance === void 0) {
|
|
10948
|
+
stderr(`${SETUP_USAGE}
|
|
10949
|
+
`);
|
|
10950
|
+
return 2;
|
|
10951
|
+
}
|
|
10952
|
+
return setupCommand({
|
|
10953
|
+
...options,
|
|
10954
|
+
tool,
|
|
10955
|
+
...parsed.values.force ? { force: true } : {},
|
|
10956
|
+
...runtime.openUrl === void 0 ? {} : { openUrl: runtime.openUrl }
|
|
10957
|
+
});
|
|
10958
|
+
}
|
|
10381
10959
|
return command === "login" ? loginCommand(options) : logoutCommand(options);
|
|
10382
10960
|
}
|
|
10383
10961
|
function isShownKind(value) {
|
|
@@ -10398,6 +10976,7 @@ export {
|
|
|
10398
10976
|
WorkingCopyValidationError,
|
|
10399
10977
|
agentAssetPath,
|
|
10400
10978
|
assertSafeApiKeyExchangeUrl,
|
|
10979
|
+
bindManifestWorkspace,
|
|
10401
10980
|
canonicalBundleFiles,
|
|
10402
10981
|
canonicalizeAgentConfig,
|
|
10403
10982
|
canonicalizeSkillConfig,
|
|
@@ -10420,6 +10999,7 @@ export {
|
|
|
10420
10999
|
readStateFile,
|
|
10421
11000
|
readWorkingCopy,
|
|
10422
11001
|
removeStaleManifestAssets,
|
|
11002
|
+
renderCatalystSkill,
|
|
10423
11003
|
resolveInstance,
|
|
10424
11004
|
resolveSkillResourceTarget,
|
|
10425
11005
|
runCli,
|