@stigmer/cli 3.15.3-dev.20260914100347 → 3.15.3-dev.20260916211208
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 +3 -4
- package/commands/apply.d.ts.map +1 -1
- package/commands/apply.js +56 -4
- package/commands/apply.js.map +1 -1
- package/commands/push.d.ts.map +1 -1
- package/commands/push.js +122 -16
- package/commands/push.js.map +1 -1
- package/commands/validate.d.ts.map +1 -1
- package/commands/validate.js +39 -7
- package/commands/validate.js.map +1 -1
- package/output/command-result.d.ts +8 -0
- package/output/command-result.d.ts.map +1 -1
- package/output/command-result.js +13 -0
- package/output/command-result.js.map +1 -1
- package/package.json +6 -5
- package/registry/aliases.d.ts.map +1 -1
- package/registry/aliases.js.map +1 -1
- package/registry/index.d.ts +3 -3
- package/registry/index.d.ts.map +1 -1
- package/registry/index.js +3 -3
- package/registry/index.js.map +1 -1
- package/registry/metadata.d.ts.map +1 -1
- package/registry/metadata.js +85 -18
- package/registry/metadata.js.map +1 -1
- package/registry/registry.d.ts.map +1 -1
- package/registry/registry.js.map +1 -1
- package/registry/verb-support.d.ts.map +1 -1
- package/registry/verb-support.js +81 -13
- package/registry/verb-support.js.map +1 -1
- package/resources/delete.d.ts.map +1 -1
- package/resources/delete.js +35 -9
- package/resources/delete.js.map +1 -1
- package/resources/get-bindings.d.ts.map +1 -1
- package/resources/get-bindings.js +56 -13
- package/resources/get-bindings.js.map +1 -1
- package/resources/list.d.ts.map +1 -1
- package/resources/list.js +62 -14
- package/resources/list.js.map +1 -1
- package/resources/plugin.d.ts +80 -0
- package/resources/plugin.d.ts.map +1 -0
- package/resources/plugin.js +305 -0
- package/resources/plugin.js.map +1 -0
- package/resources/skill.d.ts +1 -0
- package/resources/skill.d.ts.map +1 -1
- package/resources/skill.js +1 -1
- package/resources/skill.js.map +1 -1
- package/src/commands/apply.ts +133 -21
- package/src/commands/push.ts +270 -41
- package/src/commands/validate.test.ts +111 -22
- package/src/commands/validate.ts +68 -12
- package/src/output/command-result.test.ts +9 -0
- package/src/output/command-result.ts +14 -0
- package/src/registry/aliases.test.ts +24 -5
- package/src/registry/aliases.ts +6 -1
- package/src/registry/index.ts +19 -3
- package/src/registry/metadata.ts +85 -18
- package/src/registry/registry.test.ts +86 -37
- package/src/registry/registry.ts +8 -2
- package/src/registry/verb-support.ts +85 -14
- package/src/resources/delete.ts +106 -26
- package/src/resources/get-bindings.ts +95 -17
- package/src/resources/list.ts +102 -28
- package/src/resources/plugin.test.ts +233 -0
- package/src/resources/plugin.ts +432 -0
- package/src/resources/skill.ts +1 -1
package/src/commands/apply.ts
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
//
|
|
3
3
|
// File mode (-f): apply individual YAML resource files (or a directory)
|
|
4
4
|
// Declarative mode: a stigmer.yaml project directory (scan + reconcile)
|
|
5
|
+
// Plugin track: a directory holding a plugin manifest (either flag,
|
|
6
|
+
// or the working directory) installs as a plugin — the
|
|
7
|
+
// same read, zip and push as `push plugin <dir>`, so the
|
|
8
|
+
// two verbs are one code path
|
|
5
9
|
//
|
|
6
10
|
// Resources marshal strictly from YAML to full protos and apply through the raw
|
|
7
11
|
// command controllers (preserving metadata.id so updates aren't misrouted as
|
|
@@ -9,7 +13,12 @@
|
|
|
9
13
|
import type { Command } from "commander";
|
|
10
14
|
import { ensureAuthenticated, resolveOrganization } from "../config/index.js";
|
|
11
15
|
import { UsageError } from "../errors/index.js";
|
|
12
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
CommandResult,
|
|
18
|
+
type OutputFlags,
|
|
19
|
+
type OutputFormat,
|
|
20
|
+
renderResult,
|
|
21
|
+
} from "../output/index.js";
|
|
13
22
|
import { addResultFlags, globalOrg, resultFormat } from "./shared.js";
|
|
14
23
|
|
|
15
24
|
interface ApplyFlags extends OutputFlags {
|
|
@@ -30,7 +39,9 @@ export function registerApply(program: Command): void {
|
|
|
30
39
|
// parity; it is intentionally a no-op client-side. Tracked as a Go follow-up.
|
|
31
40
|
.option("--prune", "delete orphaned resources (declarative mode)", true)
|
|
32
41
|
.option("--dry-run", "validate without applying")
|
|
33
|
-
.action((options: ApplyFlags, command: Command) =>
|
|
42
|
+
.action((options: ApplyFlags, command: Command) =>
|
|
43
|
+
runApply(options, command),
|
|
44
|
+
);
|
|
34
45
|
addResultFlags(apply);
|
|
35
46
|
}
|
|
36
47
|
|
|
@@ -38,14 +49,82 @@ async function runApply(options: ApplyFlags, command: Command): Promise<void> {
|
|
|
38
49
|
const format = resultFormat(options);
|
|
39
50
|
const orgOverride = globalOrg(command);
|
|
40
51
|
|
|
52
|
+
// A plugin manifest at the given directory's root selects the plugin
|
|
53
|
+
// track before any YAML scan: a repository holding both is applied as a
|
|
54
|
+
// plugin, and its `ai.stigmer/*.yaml` documents are the plugin's, not
|
|
55
|
+
// loose resources.
|
|
56
|
+
const target =
|
|
57
|
+
options.file !== undefined && options.file !== ""
|
|
58
|
+
? options.file
|
|
59
|
+
: options.config !== undefined && options.config !== ""
|
|
60
|
+
? options.config
|
|
61
|
+
: process.cwd();
|
|
62
|
+
const { isPluginDirectory } = await import("../resources/plugin.js");
|
|
63
|
+
if (isPluginDirectory(target)) {
|
|
64
|
+
await runPluginApply(target, orgOverride, options.dryRun === true, format);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
41
68
|
if (options.file !== undefined && options.file !== "") {
|
|
42
|
-
await runFileApply(
|
|
69
|
+
await runFileApply(
|
|
70
|
+
options.file,
|
|
71
|
+
orgOverride,
|
|
72
|
+
options.dryRun === true,
|
|
73
|
+
format,
|
|
74
|
+
);
|
|
43
75
|
return;
|
|
44
76
|
}
|
|
45
77
|
|
|
46
78
|
await runDeclarativeApply(options, orgOverride, format);
|
|
47
79
|
}
|
|
48
80
|
|
|
81
|
+
// Plugin track: install or upgrade the plugin folder as one unit through the
|
|
82
|
+
// same read, zip and push `push plugin` runs; the org resolves like every
|
|
83
|
+
// other apply (--org, then the configured context).
|
|
84
|
+
async function runPluginApply(
|
|
85
|
+
dir: string,
|
|
86
|
+
orgOverride: string | undefined,
|
|
87
|
+
dryRun: boolean,
|
|
88
|
+
format: OutputFormat,
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
const plugin = await import("../resources/plugin.js");
|
|
91
|
+
const ignoreOptions = {
|
|
92
|
+
respectGitignore: true,
|
|
93
|
+
extraIgnore: [],
|
|
94
|
+
extraInclude: [],
|
|
95
|
+
};
|
|
96
|
+
if (dryRun) {
|
|
97
|
+
const { readPluginPackage } = await import("@stigmer/plugin-package");
|
|
98
|
+
const read = plugin.readPluginDirectory(dir, ignoreOptions);
|
|
99
|
+
const outcome = readPluginPackage(read.files);
|
|
100
|
+
if (!outcome.ok) {
|
|
101
|
+
throw plugin.pluginRefusal(dir, outcome.errors, outcome.warnings);
|
|
102
|
+
}
|
|
103
|
+
const result = plugin.describePackageOn(
|
|
104
|
+
CommandResult.success(
|
|
105
|
+
`Dry run: plugin '${outcome.plugin.name}' would install`,
|
|
106
|
+
),
|
|
107
|
+
outcome.plugin,
|
|
108
|
+
outcome.warnings,
|
|
109
|
+
read.stats,
|
|
110
|
+
);
|
|
111
|
+
result.hint("Run without --dry-run to install it.");
|
|
112
|
+
renderResult(result, format);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const { connectBackend } = await import("../backend.js");
|
|
116
|
+
const client = connectBackend();
|
|
117
|
+
ensureAuthenticated(client.config);
|
|
118
|
+
const org = resolveOrganization(client.config, orgOverride);
|
|
119
|
+
const outcome = await plugin.pushPlugin(client.stigmer, dir, {
|
|
120
|
+
org,
|
|
121
|
+
visibility: undefined,
|
|
122
|
+
message: "",
|
|
123
|
+
ignoreOptions,
|
|
124
|
+
});
|
|
125
|
+
renderResult(plugin.renderPushOutcome(outcome), format);
|
|
126
|
+
}
|
|
127
|
+
|
|
49
128
|
// Declarative mode: detect stigmer.yaml (walk up). Absent → atomic guidance.
|
|
50
129
|
// entry_point set → SDK/project synthesis track. Otherwise scan the project and
|
|
51
130
|
// reconcile membership. Both project tracks converge on the shared reconciler.
|
|
@@ -54,9 +133,13 @@ async function runDeclarativeApply(
|
|
|
54
133
|
orgOverride: string | undefined,
|
|
55
134
|
format: OutputFormat,
|
|
56
135
|
): Promise<void> {
|
|
57
|
-
const { detectTrack, applyDeclarative, previewDeclarative } =
|
|
136
|
+
const { detectTrack, applyDeclarative, previewDeclarative } =
|
|
137
|
+
await import("../resources/apply/declarative.js");
|
|
58
138
|
|
|
59
|
-
const startDir =
|
|
139
|
+
const startDir =
|
|
140
|
+
options.config !== undefined && options.config !== ""
|
|
141
|
+
? options.config
|
|
142
|
+
: process.cwd();
|
|
60
143
|
const detect = detectTrack(startDir);
|
|
61
144
|
|
|
62
145
|
if (detect.track === "atomic") {
|
|
@@ -82,7 +165,11 @@ async function runDeclarativeApply(
|
|
|
82
165
|
const { connectBackend } = await import("../backend.js");
|
|
83
166
|
const client = connectBackend();
|
|
84
167
|
ensureAuthenticated(client.config);
|
|
85
|
-
const org = resolveDeclarativeOrg(
|
|
168
|
+
const org = resolveDeclarativeOrg(
|
|
169
|
+
client.config,
|
|
170
|
+
detect.project?.metadata?.org,
|
|
171
|
+
orgOverride,
|
|
172
|
+
);
|
|
86
173
|
|
|
87
174
|
const result = await applyDeclarative(detect, {
|
|
88
175
|
controller: client.controller,
|
|
@@ -104,7 +191,8 @@ async function runProjectApply(
|
|
|
104
191
|
format: OutputFormat,
|
|
105
192
|
dryRun: boolean,
|
|
106
193
|
): Promise<void> {
|
|
107
|
-
const { applyProjectTrack, previewProjectTrack } =
|
|
194
|
+
const { applyProjectTrack, previewProjectTrack } =
|
|
195
|
+
await import("../resources/apply/synth/project-track.js");
|
|
108
196
|
|
|
109
197
|
if (dryRun) {
|
|
110
198
|
renderResult(previewProjectTrack(detect), format);
|
|
@@ -114,7 +202,11 @@ async function runProjectApply(
|
|
|
114
202
|
const { connectBackend } = await import("../backend.js");
|
|
115
203
|
const client = connectBackend();
|
|
116
204
|
ensureAuthenticated(client.config);
|
|
117
|
-
const org = resolveDeclarativeOrg(
|
|
205
|
+
const org = resolveDeclarativeOrg(
|
|
206
|
+
client.config,
|
|
207
|
+
detect.project?.metadata?.org,
|
|
208
|
+
orgOverride,
|
|
209
|
+
);
|
|
118
210
|
|
|
119
211
|
const result = await applyProjectTrack(detect, {
|
|
120
212
|
controller: client.controller,
|
|
@@ -134,7 +226,10 @@ function resolveDeclarativeOrg(
|
|
|
134
226
|
projectOrg: string | undefined,
|
|
135
227
|
override: string | undefined,
|
|
136
228
|
): string {
|
|
137
|
-
const resolved = resolveOrganization(
|
|
229
|
+
const resolved = resolveOrganization(
|
|
230
|
+
config,
|
|
231
|
+
override ?? (projectOrg !== "" ? projectOrg : undefined),
|
|
232
|
+
);
|
|
138
233
|
if (resolved === "") {
|
|
139
234
|
throw new UsageError(
|
|
140
235
|
"organization not set\n\n" +
|
|
@@ -153,12 +248,15 @@ async function runFileApply(
|
|
|
153
248
|
dryRun: boolean,
|
|
154
249
|
format: OutputFormat,
|
|
155
250
|
): Promise<void> {
|
|
156
|
-
const [
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
251
|
+
const [
|
|
252
|
+
{ connectBackend },
|
|
253
|
+
{ resolveApplyItems, requiresOrgContext, applyItem },
|
|
254
|
+
{ discoverAppliedMcpServers },
|
|
255
|
+
] = await Promise.all([
|
|
256
|
+
import("../backend.js"),
|
|
257
|
+
import("../resources/apply/apply.js"),
|
|
258
|
+
import("../resources/apply/discovery.js"),
|
|
259
|
+
]);
|
|
162
260
|
|
|
163
261
|
const items = resolveApplyItems(path);
|
|
164
262
|
|
|
@@ -174,31 +272,45 @@ async function runFileApply(
|
|
|
174
272
|
|
|
175
273
|
const client = connectBackend();
|
|
176
274
|
ensureAuthenticated(client.config);
|
|
177
|
-
const org = requiresOrgContext(items)
|
|
275
|
+
const org = requiresOrgContext(items)
|
|
276
|
+
? resolveOrganization(client.config, orgOverride)
|
|
277
|
+
: "";
|
|
178
278
|
|
|
179
279
|
const appliedMcpServers = [];
|
|
180
280
|
for (const item of items) {
|
|
181
281
|
const outcome = await applyItem(client.controller, item, org, false);
|
|
182
282
|
emitWarning(outcome.warning);
|
|
183
283
|
renderResult(outcome.result, format);
|
|
184
|
-
if (outcome.appliedMcpServer !== undefined)
|
|
284
|
+
if (outcome.appliedMcpServer !== undefined)
|
|
285
|
+
appliedMcpServers.push(outcome.appliedMcpServer);
|
|
185
286
|
}
|
|
186
287
|
|
|
187
|
-
await discoverAppliedMcpServers(
|
|
288
|
+
await discoverAppliedMcpServers(
|
|
289
|
+
client.stigmer,
|
|
290
|
+
appliedMcpServers,
|
|
291
|
+
org,
|
|
292
|
+
(line) => process.stderr.write(`${line}\n`),
|
|
293
|
+
);
|
|
188
294
|
}
|
|
189
295
|
|
|
190
296
|
function buildAtomicGuidance(): CommandResult {
|
|
191
|
-
const result = CommandResult.warning(
|
|
297
|
+
const result = CommandResult.warning(
|
|
298
|
+
"No stigmer.yaml found in current directory or parents",
|
|
299
|
+
);
|
|
192
300
|
result
|
|
193
301
|
.addSection("")
|
|
194
|
-
.item(
|
|
302
|
+
.item(
|
|
303
|
+
"The 'stigmer apply' command (without -f) requires a project with stigmer.yaml",
|
|
304
|
+
)
|
|
195
305
|
.item("This enables resource discovery and project-based reconciliation");
|
|
196
306
|
result
|
|
197
307
|
.addSection("For single-resource deployment, use file mode")
|
|
198
308
|
.item("stigmer apply -f agent.yaml")
|
|
199
309
|
.item("stigmer apply -f workflow.yaml")
|
|
200
310
|
.item("stigmer apply -f mcpserver.yaml");
|
|
201
|
-
result.hint(
|
|
311
|
+
result.hint(
|
|
312
|
+
"To create a project: add a stigmer.yaml with your project name, then run 'stigmer apply'",
|
|
313
|
+
);
|
|
202
314
|
return result;
|
|
203
315
|
}
|
|
204
316
|
|
package/src/commands/push.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// `stigmer push <type> [path]` — package and upload an artifact to the registry.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// Two kinds are pushable: skills (a local directory with git provenance
|
|
3
|
+
// auto-detected, a remote repo via --git-url, or a pre-built --archive) and
|
|
4
|
+
// plugins (a local plugin directory, installed or upgraded as one unit; the
|
|
5
|
+
// manifest's version is its tag, so --tag does not apply). Heavy modules are
|
|
6
|
+
// lazy-imported so `--help` stays fast.
|
|
5
7
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
6
8
|
import { tmpdir } from "node:os";
|
|
7
9
|
import { join } from "node:path";
|
|
@@ -9,11 +11,16 @@ import type { Command } from "commander";
|
|
|
9
11
|
import { ensureAuthenticated, resolveOrganization } from "../config/index.js";
|
|
10
12
|
import { UsageError } from "../errors/index.js";
|
|
11
13
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
14
|
+
import {
|
|
15
|
+
type OutputFlags,
|
|
16
|
+
renderResult as renderCommandResult,
|
|
17
|
+
} from "../output/index.js";
|
|
12
18
|
import { defaultRegistry, Verb } from "../registry/index.js";
|
|
13
|
-
import { globalOrg } from "./shared.js";
|
|
19
|
+
import { addResultFlags, globalOrg, resultFormat } from "./shared.js";
|
|
14
20
|
|
|
15
|
-
interface PushFlags {
|
|
21
|
+
interface PushFlags extends OutputFlags {
|
|
16
22
|
tag?: string;
|
|
23
|
+
visibility?: string;
|
|
17
24
|
message?: string;
|
|
18
25
|
dryRun?: boolean;
|
|
19
26
|
gitUrl?: string;
|
|
@@ -26,41 +33,96 @@ interface PushFlags {
|
|
|
26
33
|
verbose?: boolean;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
const collect = (value: string, previous: string[]): string[] => [
|
|
36
|
+
const collect = (value: string, previous: string[]): string[] => [
|
|
37
|
+
...previous,
|
|
38
|
+
value,
|
|
39
|
+
];
|
|
30
40
|
|
|
31
41
|
export function registerPush(program: Command): void {
|
|
32
|
-
program
|
|
42
|
+
const push = program
|
|
33
43
|
.command("push <type> [path]")
|
|
34
|
-
.description(
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
.description(
|
|
45
|
+
"push an artifact to the registry (supported types: skill, plugin)",
|
|
46
|
+
)
|
|
47
|
+
.option(
|
|
48
|
+
"--tag <tag>",
|
|
49
|
+
"version tag for the artifact (skills; a plugin's tag is its manifest version)",
|
|
50
|
+
"latest",
|
|
51
|
+
)
|
|
52
|
+
.option(
|
|
53
|
+
"-m, --message <message>",
|
|
54
|
+
"version message describing what changed",
|
|
55
|
+
)
|
|
56
|
+
.option(
|
|
57
|
+
"--visibility <level>",
|
|
58
|
+
"plugins: visibility for the plugin and everything it installs (private, org, public, platform)",
|
|
59
|
+
)
|
|
37
60
|
.option("--dry-run", "validate without pushing")
|
|
38
61
|
.option("--git-url <url>", "push from a remote git repository URL")
|
|
39
|
-
.option(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.option(
|
|
62
|
+
.option(
|
|
63
|
+
"--git-ref <ref>",
|
|
64
|
+
"git reference (tag, branch, or commit SHA) for remote push",
|
|
65
|
+
)
|
|
66
|
+
.option(
|
|
67
|
+
"--subdir <dir>",
|
|
68
|
+
"subdirectory within the git repository containing the artifact",
|
|
69
|
+
)
|
|
70
|
+
.option(
|
|
71
|
+
"--archive <file>",
|
|
72
|
+
"push a pre-packaged ZIP archive as-is (version hash = the file's SHA-256)",
|
|
73
|
+
)
|
|
74
|
+
.option(
|
|
75
|
+
"--ignore <pattern>",
|
|
76
|
+
"additional pattern to ignore (repeatable)",
|
|
77
|
+
collect,
|
|
78
|
+
[],
|
|
79
|
+
)
|
|
80
|
+
.option(
|
|
81
|
+
"--include <pattern>",
|
|
82
|
+
"pattern to force-include (repeatable)",
|
|
83
|
+
collect,
|
|
84
|
+
[],
|
|
85
|
+
)
|
|
44
86
|
.option("--no-gitignore", "don't respect .gitignore patterns")
|
|
45
87
|
.option("--verbose", "show detailed output including ignore decisions")
|
|
46
|
-
.action(
|
|
47
|
-
|
|
88
|
+
.action(
|
|
89
|
+
(
|
|
90
|
+
type: string,
|
|
91
|
+
path: string | undefined,
|
|
92
|
+
options: PushFlags,
|
|
93
|
+
command: Command,
|
|
94
|
+
) => runPush(type, path, options, command),
|
|
48
95
|
);
|
|
96
|
+
addResultFlags(push);
|
|
49
97
|
}
|
|
50
98
|
|
|
51
|
-
async function runPush(
|
|
99
|
+
async function runPush(
|
|
100
|
+
type: string,
|
|
101
|
+
path: string | undefined,
|
|
102
|
+
options: PushFlags,
|
|
103
|
+
command: Command,
|
|
104
|
+
): Promise<void> {
|
|
52
105
|
const info = defaultRegistry().getByAlias(type);
|
|
53
106
|
if (info === undefined) {
|
|
54
|
-
throw new UsageError(
|
|
107
|
+
throw new UsageError(
|
|
108
|
+
`unknown resource type: ${type}\n\nAvailable types: skill, plugin`,
|
|
109
|
+
);
|
|
55
110
|
}
|
|
56
111
|
if (!info.supportedVerbs.has(Verb.Push)) {
|
|
57
112
|
throw new UsageError(`${info.displayName} does not support 'push'`);
|
|
58
113
|
}
|
|
114
|
+
if (info.kind === ApiResourceKind.plugin) {
|
|
115
|
+
await runPushPlugin(path, options, command);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
59
118
|
if (info.kind !== ApiResourceKind.skill) {
|
|
60
119
|
throw new UsageError(`push not implemented for ${info.displayName}`);
|
|
61
120
|
}
|
|
62
121
|
|
|
63
|
-
const [{ connectBackend }, skill] = await Promise.all([
|
|
122
|
+
const [{ connectBackend }, skill] = await Promise.all([
|
|
123
|
+
import("../backend.js"),
|
|
124
|
+
import("../resources/skill.js"),
|
|
125
|
+
]);
|
|
64
126
|
const client = connectBackend();
|
|
65
127
|
ensureAuthenticated(client.config);
|
|
66
128
|
const org = resolveOrganization(client.config, globalOrg(command));
|
|
@@ -70,17 +132,33 @@ async function runPush(type: string, path: string | undefined, options: PushFlag
|
|
|
70
132
|
extraIgnore: options.ignore ?? [],
|
|
71
133
|
extraInclude: options.include ?? [],
|
|
72
134
|
};
|
|
73
|
-
const decisionSink =
|
|
135
|
+
const decisionSink =
|
|
136
|
+
options.verbose === true
|
|
137
|
+
? (line: string) => process.stderr.write(`${line}\n`)
|
|
138
|
+
: undefined;
|
|
74
139
|
const tag = options.tag ?? "latest";
|
|
75
140
|
const message = options.message ?? "";
|
|
76
141
|
|
|
77
142
|
if (options.archive !== undefined && options.archive !== "") {
|
|
78
|
-
await pushArchive(client.stigmer, skill, {
|
|
143
|
+
await pushArchive(client.stigmer, skill, {
|
|
144
|
+
org,
|
|
145
|
+
tag,
|
|
146
|
+
message,
|
|
147
|
+
path,
|
|
148
|
+
options,
|
|
149
|
+
});
|
|
79
150
|
return;
|
|
80
151
|
}
|
|
81
152
|
|
|
82
153
|
if (options.gitUrl !== undefined && options.gitUrl !== "") {
|
|
83
|
-
await pushRemote(client.stigmer, skill, {
|
|
154
|
+
await pushRemote(client.stigmer, skill, {
|
|
155
|
+
org,
|
|
156
|
+
tag,
|
|
157
|
+
message,
|
|
158
|
+
ignoreOptions,
|
|
159
|
+
decisionSink,
|
|
160
|
+
options,
|
|
161
|
+
});
|
|
84
162
|
return;
|
|
85
163
|
}
|
|
86
164
|
|
|
@@ -94,14 +172,104 @@ async function runPush(type: string, path: string | undefined, options: PushFlag
|
|
|
94
172
|
skill.parseSkillMetadata(directory);
|
|
95
173
|
|
|
96
174
|
if (options.dryRun === true) {
|
|
97
|
-
process.stdout.write(
|
|
175
|
+
process.stdout.write(
|
|
176
|
+
renderDryRun(
|
|
177
|
+
directory,
|
|
178
|
+
skill.analyzeDryRun(directory, ignoreOptions),
|
|
179
|
+
skill,
|
|
180
|
+
),
|
|
181
|
+
);
|
|
98
182
|
return;
|
|
99
183
|
}
|
|
100
184
|
|
|
101
|
-
const result = await skill.pushSkill(
|
|
185
|
+
const result = await skill.pushSkill(
|
|
186
|
+
client.stigmer,
|
|
187
|
+
directory,
|
|
188
|
+
org,
|
|
189
|
+
tag,
|
|
190
|
+
message,
|
|
191
|
+
ignoreOptions,
|
|
192
|
+
decisionSink,
|
|
193
|
+
);
|
|
102
194
|
process.stdout.write(renderResult(result, skill));
|
|
103
195
|
}
|
|
104
196
|
|
|
197
|
+
/**
|
|
198
|
+
* `stigmer push plugin <dir>`: install or upgrade a plugin from its folder.
|
|
199
|
+
* The folder is read through the same walk `validate -f` uses, refused
|
|
200
|
+
* offline with the same sentences, zipped deterministically and pushed;
|
|
201
|
+
* `--dry-run` stops after the offline read and prints what would install.
|
|
202
|
+
* Skill-only flags (`--archive`, `--git-url`) are a contradiction here.
|
|
203
|
+
*/
|
|
204
|
+
async function runPushPlugin(
|
|
205
|
+
path: string | undefined,
|
|
206
|
+
options: PushFlags,
|
|
207
|
+
command: Command,
|
|
208
|
+
): Promise<void> {
|
|
209
|
+
const conflicts: string[] = [];
|
|
210
|
+
if (options.archive !== undefined && options.archive !== "")
|
|
211
|
+
conflicts.push("--archive");
|
|
212
|
+
if (options.gitUrl !== undefined && options.gitUrl !== "")
|
|
213
|
+
conflicts.push("--git-url");
|
|
214
|
+
if (options.gitRef !== undefined && options.gitRef !== "")
|
|
215
|
+
conflicts.push("--git-ref");
|
|
216
|
+
if (options.subdir !== undefined && options.subdir !== "")
|
|
217
|
+
conflicts.push("--subdir");
|
|
218
|
+
if (conflicts.length > 0) {
|
|
219
|
+
throw new UsageError(
|
|
220
|
+
`push plugin takes a plugin directory and cannot be combined with: ${conflicts.join(", ")}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const format = resultFormat(options);
|
|
224
|
+
const directory = path !== undefined && path !== "" ? path : process.cwd();
|
|
225
|
+
const plugin = await import("../resources/plugin.js");
|
|
226
|
+
if (!plugin.isPluginDirectory(directory)) {
|
|
227
|
+
throw new UsageError(
|
|
228
|
+
`${directory} is not a plugin directory\n\nA plugin directory holds one of: ${plugin.PLUGIN_MANIFEST_PATHS.join(", ")}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
const ignoreOptions = {
|
|
232
|
+
respectGitignore: options.gitignore !== false,
|
|
233
|
+
extraIgnore: options.ignore ?? [],
|
|
234
|
+
extraInclude: options.include ?? [],
|
|
235
|
+
};
|
|
236
|
+
const { parseVisibility } = await import("../resources/skill.js");
|
|
237
|
+
const visibility = parseVisibility(options.visibility, "--visibility");
|
|
238
|
+
|
|
239
|
+
if (options.dryRun === true) {
|
|
240
|
+
const { readPluginPackage } = await import("@stigmer/plugin-package");
|
|
241
|
+
const read = plugin.readPluginDirectory(directory, ignoreOptions);
|
|
242
|
+
const outcome = readPluginPackage(read.files);
|
|
243
|
+
if (!outcome.ok) {
|
|
244
|
+
throw plugin.pluginRefusal(directory, outcome.errors, outcome.warnings);
|
|
245
|
+
}
|
|
246
|
+
const { CommandResult } = await import("../output/index.js");
|
|
247
|
+
const result = plugin.describePackageOn(
|
|
248
|
+
CommandResult.success(
|
|
249
|
+
`Dry run: plugin '${outcome.plugin.name}' would install`,
|
|
250
|
+
),
|
|
251
|
+
outcome.plugin,
|
|
252
|
+
outcome.warnings,
|
|
253
|
+
read.stats,
|
|
254
|
+
);
|
|
255
|
+
result.hint("Run without --dry-run to install it.");
|
|
256
|
+
renderCommandResult(result, format);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const { connectBackend } = await import("../backend.js");
|
|
261
|
+
const client = connectBackend();
|
|
262
|
+
ensureAuthenticated(client.config);
|
|
263
|
+
const org = resolveOrganization(client.config, globalOrg(command));
|
|
264
|
+
const outcome = await plugin.pushPlugin(client.stigmer, directory, {
|
|
265
|
+
org,
|
|
266
|
+
visibility,
|
|
267
|
+
message: options.message ?? "",
|
|
268
|
+
ignoreOptions,
|
|
269
|
+
});
|
|
270
|
+
renderCommandResult(plugin.renderPushOutcome(outcome), format);
|
|
271
|
+
}
|
|
272
|
+
|
|
105
273
|
interface ArchiveContext {
|
|
106
274
|
org: string;
|
|
107
275
|
tag: string;
|
|
@@ -123,9 +291,12 @@ async function pushArchive(
|
|
|
123
291
|
): Promise<void> {
|
|
124
292
|
const conflicts: string[] = [];
|
|
125
293
|
if (ctx.path !== undefined && ctx.path !== "") conflicts.push("[path]");
|
|
126
|
-
if (ctx.options.gitUrl !== undefined && ctx.options.gitUrl !== "")
|
|
127
|
-
|
|
128
|
-
if (ctx.options.
|
|
294
|
+
if (ctx.options.gitUrl !== undefined && ctx.options.gitUrl !== "")
|
|
295
|
+
conflicts.push("--git-url");
|
|
296
|
+
if (ctx.options.gitRef !== undefined && ctx.options.gitRef !== "")
|
|
297
|
+
conflicts.push("--git-ref");
|
|
298
|
+
if (ctx.options.subdir !== undefined && ctx.options.subdir !== "")
|
|
299
|
+
conflicts.push("--subdir");
|
|
129
300
|
if ((ctx.options.ignore ?? []).length > 0) conflicts.push("--ignore");
|
|
130
301
|
if ((ctx.options.include ?? []).length > 0) conflicts.push("--include");
|
|
131
302
|
if (ctx.options.gitignore === false) conflicts.push("--no-gitignore");
|
|
@@ -138,11 +309,23 @@ async function pushArchive(
|
|
|
138
309
|
|
|
139
310
|
const archivePath = ctx.options.archive ?? "";
|
|
140
311
|
if (ctx.options.dryRun === true) {
|
|
141
|
-
process.stdout.write(
|
|
312
|
+
process.stdout.write(
|
|
313
|
+
renderArchiveDryRun(
|
|
314
|
+
archivePath,
|
|
315
|
+
skill.readSkillArchive(archivePath),
|
|
316
|
+
skill,
|
|
317
|
+
),
|
|
318
|
+
);
|
|
142
319
|
return;
|
|
143
320
|
}
|
|
144
321
|
|
|
145
|
-
const result = await skill.pushSkillFromArchive(
|
|
322
|
+
const result = await skill.pushSkillFromArchive(
|
|
323
|
+
client,
|
|
324
|
+
archivePath,
|
|
325
|
+
ctx.org,
|
|
326
|
+
ctx.tag,
|
|
327
|
+
ctx.message,
|
|
328
|
+
);
|
|
146
329
|
process.stdout.write(renderResult(result, skill));
|
|
147
330
|
}
|
|
148
331
|
|
|
@@ -173,7 +356,11 @@ interface RemoteContext {
|
|
|
173
356
|
org: string;
|
|
174
357
|
tag: string;
|
|
175
358
|
message: string;
|
|
176
|
-
ignoreOptions: {
|
|
359
|
+
ignoreOptions: {
|
|
360
|
+
respectGitignore: boolean;
|
|
361
|
+
extraIgnore: string[];
|
|
362
|
+
extraInclude: string[];
|
|
363
|
+
};
|
|
177
364
|
decisionSink?: (line: string) => void;
|
|
178
365
|
options: PushFlags;
|
|
179
366
|
}
|
|
@@ -206,7 +393,15 @@ async function pushRemote(
|
|
|
206
393
|
const result = await skill.pushSkillFromClone(
|
|
207
394
|
client,
|
|
208
395
|
skillDir,
|
|
209
|
-
{
|
|
396
|
+
{
|
|
397
|
+
gitUrl,
|
|
398
|
+
gitRef,
|
|
399
|
+
subdir,
|
|
400
|
+
org: ctx.org,
|
|
401
|
+
tag: ctx.tag,
|
|
402
|
+
message: ctx.message,
|
|
403
|
+
options: ctx.ignoreOptions,
|
|
404
|
+
},
|
|
210
405
|
ctx.decisionSink,
|
|
211
406
|
);
|
|
212
407
|
process.stdout.write(renderResult(result, skill));
|
|
@@ -215,8 +410,15 @@ async function pushRemote(
|
|
|
215
410
|
}
|
|
216
411
|
}
|
|
217
412
|
|
|
218
|
-
function renderResult(
|
|
219
|
-
|
|
413
|
+
function renderResult(
|
|
414
|
+
result: import("../resources/skill.js").PushResult,
|
|
415
|
+
skill: typeof import("../resources/skill.js"),
|
|
416
|
+
): string {
|
|
417
|
+
const status = result.isNewResource
|
|
418
|
+
? "new resource"
|
|
419
|
+
: result.versionChanged
|
|
420
|
+
? "new version"
|
|
421
|
+
: "unchanged";
|
|
220
422
|
const lines = [
|
|
221
423
|
"",
|
|
222
424
|
`Pushed skill '${result.skillName}'`,
|
|
@@ -250,25 +452,52 @@ function renderDryRun(
|
|
|
250
452
|
`Directories skipped: ${stats.dirsSkipped}`,
|
|
251
453
|
];
|
|
252
454
|
if (analysis.sampleIncluded.length > 0) {
|
|
253
|
-
lines.push(
|
|
455
|
+
lines.push(
|
|
456
|
+
"",
|
|
457
|
+
"Sample included:",
|
|
458
|
+
...analysis.sampleIncluded.map((f) => ` + ${f}`),
|
|
459
|
+
);
|
|
254
460
|
}
|
|
255
461
|
if (analysis.sampleIgnored.length > 0) {
|
|
256
|
-
lines.push(
|
|
462
|
+
lines.push(
|
|
463
|
+
"",
|
|
464
|
+
"Sample ignored:",
|
|
465
|
+
...analysis.sampleIgnored.map((f) => ` - ${f}`),
|
|
466
|
+
);
|
|
257
467
|
}
|
|
258
468
|
lines.push("", "Run without --dry-run to push the skill artifact.", "");
|
|
259
469
|
return `${lines.join("\n")}\n`;
|
|
260
470
|
}
|
|
261
471
|
|
|
262
|
-
function renderRemoteDryRun(
|
|
263
|
-
|
|
472
|
+
function renderRemoteDryRun(
|
|
473
|
+
gitUrl: string,
|
|
474
|
+
gitRef: string,
|
|
475
|
+
subdir: string,
|
|
476
|
+
ctx: RemoteContext,
|
|
477
|
+
): string {
|
|
478
|
+
const lines = [
|
|
479
|
+
"",
|
|
480
|
+
"Dry run mode - would push skill from remote git repository:",
|
|
481
|
+
"",
|
|
482
|
+
"Git Source:",
|
|
483
|
+
` URL: ${gitUrl}`,
|
|
484
|
+
];
|
|
264
485
|
if (gitRef !== "") lines.push(` Ref: ${gitRef}`);
|
|
265
486
|
if (subdir !== "") lines.push(` Subdir: ${subdir}`);
|
|
266
487
|
lines.push(` Tag: ${ctx.tag}`, "", "Ignore Configuration:");
|
|
267
|
-
lines.push(
|
|
488
|
+
lines.push(
|
|
489
|
+
` Gitignore: ${ctx.ignoreOptions.respectGitignore ? "enabled" : "disabled"}`,
|
|
490
|
+
);
|
|
268
491
|
lines.push(" Security defaults: enabled");
|
|
269
492
|
lines.push(" Stigmerignore: will load if present");
|
|
270
|
-
if (ctx.ignoreOptions.extraIgnore.length > 0)
|
|
271
|
-
|
|
493
|
+
if (ctx.ignoreOptions.extraIgnore.length > 0)
|
|
494
|
+
lines.push(
|
|
495
|
+
` Extra ignore: ${ctx.ignoreOptions.extraIgnore.join(", ")}`,
|
|
496
|
+
);
|
|
497
|
+
if (ctx.ignoreOptions.extraInclude.length > 0)
|
|
498
|
+
lines.push(
|
|
499
|
+
` Force include: ${ctx.ignoreOptions.extraInclude.join(", ")}`,
|
|
500
|
+
);
|
|
272
501
|
lines.push(
|
|
273
502
|
"",
|
|
274
503
|
"Note: Full analysis requires cloning the repository.",
|