@stigmer/cli 3.16.0 → 3.16.1-dev.20260917224426

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.
Files changed (171) hide show
  1. package/commands/agent-exec-flags.d.ts +2 -5
  2. package/commands/agent-exec-flags.d.ts.map +1 -1
  3. package/commands/agent-exec-flags.js +6 -13
  4. package/commands/agent-exec-flags.js.map +1 -1
  5. package/commands/connect.js +1 -1
  6. package/commands/connect.js.map +1 -1
  7. package/commands/install.d.ts +3 -0
  8. package/commands/install.d.ts.map +1 -0
  9. package/commands/install.js +68 -0
  10. package/commands/install.js.map +1 -0
  11. package/commands/marketplace.d.ts +3 -0
  12. package/commands/marketplace.d.ts.map +1 -0
  13. package/commands/marketplace.js +94 -0
  14. package/commands/marketplace.js.map +1 -0
  15. package/commands/push.js +2 -7
  16. package/commands/push.js.map +1 -1
  17. package/commands/run.js +0 -3
  18. package/commands/run.js.map +1 -1
  19. package/config/config.d.ts +19 -0
  20. package/config/config.d.ts.map +1 -1
  21. package/config/config.js +15 -0
  22. package/config/config.js.map +1 -1
  23. package/config/index.d.ts +1 -1
  24. package/config/index.d.ts.map +1 -1
  25. package/config/index.js.map +1 -1
  26. package/local/bootstrap.d.ts +21 -0
  27. package/local/bootstrap.d.ts.map +1 -0
  28. package/local/bootstrap.js +88 -0
  29. package/local/bootstrap.js.map +1 -0
  30. package/local/daemon/launch.d.ts +2 -2
  31. package/local/daemon/launch.d.ts.map +1 -1
  32. package/local/daemon/launch.js +4 -34
  33. package/local/daemon/launch.js.map +1 -1
  34. package/local/daemon/process.d.ts +1 -1
  35. package/local/plugins/defaults.d.ts +35 -0
  36. package/local/plugins/defaults.d.ts.map +1 -0
  37. package/local/plugins/defaults.js +83 -0
  38. package/local/plugins/defaults.js.map +1 -0
  39. package/local/seedpack/content.d.ts +2 -1
  40. package/local/seedpack/content.d.ts.map +1 -1
  41. package/local/seedpack/content.js +5 -34
  42. package/local/seedpack/content.js.map +1 -1
  43. package/marketplace/config.d.ts +59 -0
  44. package/marketplace/config.d.ts.map +1 -0
  45. package/marketplace/config.js +162 -0
  46. package/marketplace/config.js.map +1 -0
  47. package/marketplace/github.d.ts +31 -0
  48. package/marketplace/github.d.ts.map +1 -0
  49. package/marketplace/github.js +203 -0
  50. package/marketplace/github.js.map +1 -0
  51. package/marketplace/index.d.ts +8 -0
  52. package/marketplace/index.d.ts.map +1 -0
  53. package/marketplace/index.js +12 -0
  54. package/marketplace/index.js.map +1 -0
  55. package/marketplace/install.d.ts +28 -0
  56. package/marketplace/install.d.ts.map +1 -0
  57. package/marketplace/install.js +109 -0
  58. package/marketplace/install.js.map +1 -0
  59. package/marketplace/materialize.d.ts +19 -0
  60. package/marketplace/materialize.d.ts.map +1 -0
  61. package/marketplace/materialize.js +53 -0
  62. package/marketplace/materialize.js.map +1 -0
  63. package/marketplace/official.d.ts +28 -0
  64. package/marketplace/official.d.ts.map +1 -0
  65. package/marketplace/official.js +86 -0
  66. package/marketplace/official.js.map +1 -0
  67. package/marketplace/read.d.ts +17 -0
  68. package/marketplace/read.d.ts.map +1 -0
  69. package/marketplace/read.js +44 -0
  70. package/marketplace/read.js.map +1 -0
  71. package/marketplace/ref.d.ts +13 -0
  72. package/marketplace/ref.d.ts.map +1 -0
  73. package/marketplace/ref.js +54 -0
  74. package/marketplace/ref.js.map +1 -0
  75. package/marketplace/render.d.ts +22 -0
  76. package/marketplace/render.d.ts.map +1 -0
  77. package/marketplace/render.js +118 -0
  78. package/marketplace/render.js.map +1 -0
  79. package/output/ndjson.js +1 -1
  80. package/output/ndjson.js.map +1 -1
  81. package/package.json +7 -6
  82. package/program.d.ts.map +1 -1
  83. package/program.js +4 -2
  84. package/program.js.map +1 -1
  85. package/resources/ignore/index.d.ts +2 -4
  86. package/resources/ignore/index.d.ts.map +1 -1
  87. package/resources/ignore/index.js +5 -5
  88. package/resources/ignore/index.js.map +1 -1
  89. package/resources/ignore/matcher.d.ts +2 -35
  90. package/resources/ignore/matcher.d.ts.map +1 -1
  91. package/resources/ignore/matcher.js +25 -109
  92. package/resources/ignore/matcher.js.map +1 -1
  93. package/resources/plugin.d.ts +50 -18
  94. package/resources/plugin.d.ts.map +1 -1
  95. package/resources/plugin.js +73 -74
  96. package/resources/plugin.js.map +1 -1
  97. package/resources/run/agent-exec.js +1 -1
  98. package/resources/run/agent-exec.js.map +1 -1
  99. package/resources/run/prepare.d.ts +8 -19
  100. package/resources/run/prepare.d.ts.map +1 -1
  101. package/resources/run/prepare.js +4 -4
  102. package/resources/run/prepare.js.map +1 -1
  103. package/resources/skill.d.ts +2 -2
  104. package/resources/skill.d.ts.map +1 -1
  105. package/resources/skill.js +6 -10
  106. package/resources/skill.js.map +1 -1
  107. package/src/commands/agent-exec-flags.ts +6 -12
  108. package/src/commands/connect.ts +1 -1
  109. package/src/commands/install.test.ts +289 -0
  110. package/src/commands/install.ts +108 -0
  111. package/src/commands/marketplace.test.ts +200 -0
  112. package/src/commands/marketplace.ts +131 -0
  113. package/src/commands/push.ts +5 -10
  114. package/src/commands/run.ts +0 -3
  115. package/src/config/config.ts +37 -0
  116. package/src/config/index.ts +1 -0
  117. package/src/local/bootstrap.test.ts +71 -0
  118. package/src/local/bootstrap.ts +133 -0
  119. package/src/local/daemon/launch.ts +5 -40
  120. package/src/local/daemon/process.ts +1 -1
  121. package/src/local/plugins/defaults.test.ts +238 -0
  122. package/src/local/plugins/defaults.ts +135 -0
  123. package/src/local/seedpack/content.ts +11 -42
  124. package/src/marketplace/__fixtures__/cursor-marketplace.ts +62 -0
  125. package/src/marketplace/config.test.ts +245 -0
  126. package/src/marketplace/config.ts +236 -0
  127. package/src/marketplace/github.test.ts +318 -0
  128. package/src/marketplace/github.ts +296 -0
  129. package/src/marketplace/index.ts +54 -0
  130. package/src/marketplace/install.test.ts +194 -0
  131. package/src/marketplace/install.ts +159 -0
  132. package/src/marketplace/materialize.ts +80 -0
  133. package/src/marketplace/official.test.ts +107 -0
  134. package/src/marketplace/official.ts +125 -0
  135. package/src/marketplace/read.test.ts +94 -0
  136. package/src/marketplace/read.ts +76 -0
  137. package/src/marketplace/ref.test.ts +179 -0
  138. package/src/marketplace/ref.ts +74 -0
  139. package/src/marketplace/render.ts +154 -0
  140. package/src/output/ndjson.ts +1 -1
  141. package/src/program.test.ts +4 -13
  142. package/src/program.ts +4 -2
  143. package/src/resources/ignore/ignore.test.ts +23 -100
  144. package/src/resources/ignore/index.ts +11 -8
  145. package/src/resources/ignore/matcher.ts +33 -136
  146. package/src/resources/plugin.test.ts +35 -6
  147. package/src/resources/plugin.ts +132 -85
  148. package/src/resources/run/agent-exec.ts +1 -1
  149. package/src/resources/run/prepare.test.ts +4 -18
  150. package/src/resources/run/prepare.ts +12 -24
  151. package/src/resources/skill.ts +6 -10
  152. package/commands/draft.d.ts +0 -3
  153. package/commands/draft.d.ts.map +0 -1
  154. package/commands/draft.js +0 -115
  155. package/commands/draft.js.map +0 -1
  156. package/resources/ignore/defaults.d.ts +0 -2
  157. package/resources/ignore/defaults.d.ts.map +0 -1
  158. package/resources/ignore/defaults.js +0 -140
  159. package/resources/ignore/defaults.js.map +0 -1
  160. package/resources/ignore/match.d.ts +0 -3
  161. package/resources/ignore/match.d.ts.map +0 -1
  162. package/resources/ignore/match.js +0 -166
  163. package/resources/ignore/match.js.map +0 -1
  164. package/resources/ignore/pattern.d.ts +0 -13
  165. package/resources/ignore/pattern.d.ts.map +0 -1
  166. package/resources/ignore/pattern.js +0 -118
  167. package/resources/ignore/pattern.js.map +0 -1
  168. package/src/commands/draft.ts +0 -189
  169. package/src/resources/ignore/defaults.ts +0 -155
  170. package/src/resources/ignore/match.ts +0 -158
  171. package/src/resources/ignore/pattern.ts +0 -131
@@ -2,37 +2,45 @@
2
2
  // something the CLI can print.
3
3
  //
4
4
  // `@stigmer/plugin-package` is pure over a `PluginFiles` list; this module is
5
- // the CLI's edge for it. The walk is the skill packager's discipline
6
- // (`createSkillZip`): sorted entries, symlinks never followed, the same
7
- // gitignore-compatible matcher deciding inclusion, so what `validate` reads
8
- // is exactly what a push of the same directory would package. Sizes come
9
- // from `stat` so the library can refuse an over-cap document before reading
10
- // it; the library re-checks the bytes it gets back.
5
+ // the CLI's edge for it. The walk lists every file under the directory
6
+ // (symlinks never followed, sizes from `stat` so the library can refuse an
7
+ // over-cap document before reading it) and hands the list to the shared
8
+ // selection rule in `@stigmer/plugin-package/client`, which decides
9
+ // inclusion and order the same way for every client: what `validate` reads
10
+ // here is exactly what a push of the same directory packages, and exactly
11
+ // what the console packages from the same tree on a marketplace host.
11
12
  //
12
13
  // `describePlugin` is the JSON projection for `--json`: the normalised
13
14
  // package with overlay documents reduced to their paths (the bytes are the
14
15
  // user's own files, and a byte array serialises badly).
15
16
  //
16
- // The push half: the same walked file list, zipped the way the skill
17
- // packager zips (deterministic bytes, so the server's digest is a function
18
- // of content), pushed through the SDK's routed plugin client, and the
19
- // members read back for the install summary. `validate -f`, `push plugin
20
- // --dry-run` and `push plugin` share one description renderer so the
21
- // author reads one vocabulary at every step.
17
+ // The push half is two steps with a value between them: `preparePluginPush`
18
+ // (the same selected file list, archived and digested by the shared module,
19
+ // so the bytes and their SHA-256 are a function of content alone and equal
20
+ // what the console computes for the same tree) and `pushPrepared` (the SDK's
21
+ // routed plugin client, then the members read back for the install summary). The value in between is what `push plugin
22
+ // --dry-run` prints, what `stigmer install` pushes from a marketplace entry,
23
+ // and what `stigmer up` compares with the server's digest before deciding
24
+ // to push. `validate -f`, `push plugin --dry-run` and `push plugin` share one
25
+ // description renderer so the author reads one vocabulary at every step.
22
26
 
23
27
  import { readdirSync, readFileSync, statSync } from "node:fs";
24
28
  import { join } from "node:path";
25
29
  import { create, toJson } from "@bufbuild/protobuf";
26
- import { zipSync } from "fflate";
27
30
  import {
28
31
  MANIFEST_LOCATIONS,
29
32
  readPluginPackage,
30
- type PluginDialect,
31
- type PluginFileEntry,
32
33
  type PluginFiles,
33
34
  type PluginFinding,
34
35
  type PluginPackage,
35
36
  } from "@stigmer/plugin-package";
37
+ import {
38
+ DIALECT_LABELS,
39
+ archivePlugin,
40
+ digestArchive,
41
+ selectPluginFiles,
42
+ type CandidateFile,
43
+ } from "@stigmer/plugin-package/client";
36
44
  import {
37
45
  PluginSchema,
38
46
  type Plugin,
@@ -50,13 +58,14 @@ import { UsageError } from "../errors/index.js";
50
58
  import { CommandResult } from "../output/index.js";
51
59
  import { createMatcher } from "./ignore/index.js";
52
60
  import {
53
- DETERMINISTIC_ZIP_MTIME,
54
61
  formatBytes,
55
62
  shortHash,
56
63
  type IgnoreOptions,
57
64
  type ZipStats,
58
65
  } from "./skill.js";
59
66
 
67
+ export { DIALECT_LABELS };
68
+
60
69
  /** The four manifest locations; a directory holding any of them is a plugin. */
61
70
  export const PLUGIN_MANIFEST_PATHS: readonly string[] =
62
71
  Object.values(MANIFEST_LOCATIONS);
@@ -90,7 +99,14 @@ export const DEFAULT_IGNORE_OPTIONS: IgnoreOptions = {
90
99
  extraInclude: [],
91
100
  };
92
101
 
93
- /** Walk `dir` through the ignore matcher into a `PluginFiles`. */
102
+ /**
103
+ * List the files under `dir` and select through the shared rule. The walk
104
+ * prunes a directory the matcher ignores before descending (a
105
+ * `node_modules/` is never stat'ed), and the selection applies the same
106
+ * matcher to what remains, so the CLI and the console agree on every
107
+ * decision and only the CLI pays the filesystem. Symlinks are neither
108
+ * files nor directories to `readdirSync` and are never followed.
109
+ */
94
110
  export function readPluginDirectory(
95
111
  dir: string,
96
112
  options: IgnoreOptions = DEFAULT_IGNORE_OPTIONS,
@@ -102,50 +118,38 @@ export function readPluginDirectory(
102
118
  extraIgnore: options.extraIgnore,
103
119
  extraInclude: options.extraInclude,
104
120
  });
105
- const stats: ZipStats = {
106
- filesIncluded: 0,
107
- filesIgnored: 0,
108
- dirsSkipped: 0,
109
- totalSize: 0,
110
- };
111
- const entries: PluginFileEntry[] = [];
112
-
121
+ let dirsSkipped = 0;
122
+ const candidates: CandidateFile[] = [];
113
123
  const walk = (currentDir: string, prefix: string): void => {
114
- const dirents = readdirSync(currentDir, { withFileTypes: true }).sort(
115
- (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
116
- );
117
- for (const dirent of dirents) {
124
+ for (const dirent of readdirSync(currentDir, { withFileTypes: true })) {
118
125
  const relPath = prefix === "" ? dirent.name : `${prefix}/${dirent.name}`;
119
- const full = join(currentDir, dirent.name);
120
126
  if (dirent.isDirectory()) {
121
- if (matcher.matchWithReason(relPath, true).ignored) {
122
- stats.dirsSkipped++;
127
+ if (matcher.match(relPath, true)) {
128
+ dirsSkipped++;
123
129
  continue;
124
130
  }
125
- walk(full, relPath);
131
+ walk(join(currentDir, dirent.name), relPath);
126
132
  continue;
127
133
  }
128
- // Symlinks are neither files nor directories here and are never followed.
129
134
  if (!dirent.isFile()) continue;
130
- if (matcher.matchWithReason(relPath, false).ignored) {
131
- stats.filesIgnored++;
132
- continue;
133
- }
134
- const size = statSync(full).size;
135
- entries.push({ path: relPath, size });
136
- stats.filesIncluded++;
137
- stats.totalSize += size;
135
+ candidates.push({
136
+ path: relPath,
137
+ size: statSync(join(currentDir, dirent.name)).size,
138
+ });
138
139
  }
139
140
  };
140
141
  walk(dir, "");
141
142
 
143
+ const read = (path: string): Uint8Array =>
144
+ new Uint8Array(readFileSync(join(dir, ...path.split("/"))));
145
+ const selection = selectPluginFiles(candidates, read, {
146
+ respectGitignore: options.respectGitignore,
147
+ extraIgnore: options.extraIgnore,
148
+ extraInclude: options.extraInclude,
149
+ });
142
150
  return {
143
- files: {
144
- entries,
145
- read: (path) =>
146
- new Uint8Array(readFileSync(join(dir, ...path.split("/")))),
147
- },
148
- stats,
151
+ files: selection.files,
152
+ stats: { ...selection.stats, dirsSkipped },
149
153
  };
150
154
  }
151
155
 
@@ -196,14 +200,6 @@ export function describePlugin(
196
200
 
197
201
  // ─── Rendering, shared by `validate -f <dir>` and `push plugin --dry-run` ────
198
202
 
199
- /** Human labels for the four dialects, in the vocabulary the docs use. */
200
- export const DIALECT_LABELS: Readonly<Record<PluginDialect, string>> = {
201
- "agent-plugins": "Agent Plugins 1.0",
202
- claude: "Claude Code plugin",
203
- cursor: "Cursor plugin",
204
- codex: "Codex plugin",
205
- };
206
-
207
203
  /** The library's refusal as the CLI's one UsageError: every problem, then every warning. */
208
204
  export function pluginRefusal(
209
205
  dir: string,
@@ -290,24 +286,63 @@ function named(items: readonly string[]): string {
290
286
  // ─── Push ────────────────────────────────────────────────────────────────
291
287
 
292
288
  /**
293
- * The archive a push sends: the SAME file list `validate` read, zipped
294
- * deterministically (sorted entries, one DOS-epoch mtime, the skill
295
- * packager's level), so the digest the server records is a pure function
296
- * of the plugin's content and a second push of an unchanged directory is
297
- * the server's no-op.
289
+ * A plugin directory read, validated and zipped, but not yet pushed. The
290
+ * step between "a folder" and "a push" is its own value because two callers
291
+ * need to look at it first: `push plugin --dry-run` prints it, and `stigmer
292
+ * up` compares `digest` with what the server already holds before deciding
293
+ * whether a push is needed at all.
298
294
  */
299
- export function zipPluginFiles(files: PluginFiles): Uint8Array {
300
- const tree: Record<string, Uint8Array> = {};
301
- for (const entry of files.entries) {
302
- tree[entry.path] = files.read(entry.path);
295
+ export interface PreparedPluginPush {
296
+ /** The directory that was read, for sentences. */
297
+ readonly dir: string;
298
+ readonly files: PluginFiles;
299
+ readonly stats: ZipStats;
300
+ readonly plugin: PluginPackage;
301
+ readonly warnings: readonly PluginFinding[];
302
+ /** The exact bytes a push sends. */
303
+ readonly archive: Uint8Array;
304
+ /**
305
+ * SHA-256 of `archive`, lowercase hex: the identity the server records as
306
+ * `status.digest` (its digest is over the bytes it receives, and these are
307
+ * those bytes), so a client can know "already installed" without pushing.
308
+ */
309
+ readonly digest: string;
310
+ }
311
+
312
+ /**
313
+ * Read and validate offline (the same refusal `validate` prints, before a
314
+ * byte moves), then zip. The server re-validates with the same library; the
315
+ * offline pass exists so an author never waits on the network to hear a
316
+ * sentence the CLI could say.
317
+ */
318
+ export async function preparePluginPush(
319
+ dir: string,
320
+ ignoreOptions: IgnoreOptions = DEFAULT_IGNORE_OPTIONS,
321
+ ): Promise<PreparedPluginPush> {
322
+ const directory = readPluginDirectory(dir, ignoreOptions);
323
+ const outcome = readPluginPackage(directory.files);
324
+ if (!outcome.ok) {
325
+ throw pluginRefusal(dir, outcome.errors, outcome.warnings);
303
326
  }
304
- return zipSync(tree, { level: 6, mtime: DETERMINISTIC_ZIP_MTIME });
327
+ const archive = archivePlugin(directory.files);
328
+ return {
329
+ dir,
330
+ files: directory.files,
331
+ stats: directory.stats,
332
+ plugin: outcome.plugin,
333
+ warnings: outcome.warnings,
334
+ archive,
335
+ digest: await digestArchive(archive),
336
+ };
305
337
  }
306
338
 
307
- export interface PushPluginOptions {
339
+ export interface PushPreparedOptions {
308
340
  readonly org: string;
309
341
  readonly visibility: ApiResourceVisibility | undefined;
310
342
  readonly message: string;
343
+ }
344
+
345
+ export interface PushPluginOptions extends PushPreparedOptions {
311
346
  readonly ignoreOptions: IgnoreOptions;
312
347
  }
313
348
 
@@ -317,27 +352,16 @@ export interface PushPluginOutcome {
317
352
  readonly archiveBytes: number;
318
353
  }
319
354
 
320
- /**
321
- * Read, validate offline (the same refusal `validate` prints, before a byte
322
- * moves), zip, push, then read the members back. The server re-validates
323
- * with the same library; the offline pass exists so an author never waits
324
- * on the network to hear a sentence the CLI could say.
325
- */
326
- export async function pushPlugin(
355
+ /** Push a prepared archive, then read the members back for the install summary. */
356
+ export async function pushPrepared(
327
357
  client: Stigmer,
328
- dir: string,
329
- options: PushPluginOptions,
358
+ prepared: PreparedPluginPush,
359
+ options: PushPreparedOptions,
330
360
  ): Promise<PushPluginOutcome> {
331
- const directory = readPluginDirectory(dir, options.ignoreOptions);
332
- const outcome = readPluginPackage(directory.files);
333
- if (!outcome.ok) {
334
- throw pluginRefusal(dir, outcome.errors, outcome.warnings);
335
- }
336
- const archive = zipPluginFiles(directory.files);
337
361
  const plugin = await client.plugin.push(
338
362
  create(PushPluginRequestSchema, {
339
363
  org: options.org,
340
- artifact: archive,
364
+ artifact: prepared.archive,
341
365
  message: options.message,
342
366
  ...(options.visibility !== undefined && {
343
367
  visibility: options.visibility,
@@ -346,11 +370,32 @@ export async function pushPlugin(
346
370
  );
347
371
  const members = (await client.plugin.listMembers(plugin.metadata?.id ?? ""))
348
372
  .members;
349
- return { plugin, members, archiveBytes: archive.length };
373
+ return { plugin, members, archiveBytes: prepared.archive.length };
374
+ }
375
+
376
+ /** `push plugin <dir>` in one call: prepare, then push. */
377
+ export async function pushPlugin(
378
+ client: Stigmer,
379
+ dir: string,
380
+ options: PushPluginOptions,
381
+ ): Promise<PushPluginOutcome> {
382
+ return pushPrepared(
383
+ client,
384
+ await preparePluginPush(dir, options.ignoreOptions),
385
+ options,
386
+ );
387
+ }
388
+
389
+ export interface RenderPushOptions {
390
+ /** Where the archive came from when it was not a folder: the marketplace's name and source. */
391
+ readonly installedFrom?: string;
350
392
  }
351
393
 
352
394
  /** The install as the user reads it: what landed, what was skipped, what to know. */
353
- export function renderPushOutcome(outcome: PushPluginOutcome): CommandResult {
395
+ export function renderPushOutcome(
396
+ outcome: PushPluginOutcome,
397
+ options: RenderPushOptions = {},
398
+ ): CommandResult {
354
399
  const { plugin, members } = outcome;
355
400
  const warnings = plugin.status?.warnings ?? [];
356
401
  const counts = plugin.status?.materialized;
@@ -380,6 +425,8 @@ export function renderPushOutcome(outcome: PushPluginOutcome): CommandResult {
380
425
  plugin.spec === undefined ? "" : dialectLabel(plugin.spec.dialect),
381
426
  );
382
427
  about.field("Size", formatBytes(outcome.archiveBytes));
428
+ if (options.installedFrom !== undefined)
429
+ about.field("Marketplace", options.installedFrom);
383
430
 
384
431
  const installed = result.addSection("Installed");
385
432
  for (const kind of [
@@ -2,7 +2,7 @@
2
2
  // run_agent_exec.go): create the agent execution (one call — a workspace rides
3
3
  // the embedded session_spec and the backend bootstraps the session), then
4
4
  // either detach (print header + re-attach hint) or stream and optionally
5
- // download artifacts. Shared by `run` and `draft`.
5
+ // download artifacts, for `run`.
6
6
 
7
7
  import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
8
8
  import type { BackendClient } from "../../client/index.js";
@@ -149,8 +149,8 @@ function countingClient(preferences: StubPreferences): { client: Stigmer; calls:
149
149
  return { client, calls: () => calls };
150
150
  }
151
151
 
152
- /** prepareAgentExec options as `run` passes them: the kind is served here + harness opt-in. */
153
- const SERVED_RUN_OPTIONS = { accountPreferencesAvailable: true, applyAccountHarnessDefault: true } as const;
152
+ /** prepareAgentExec options as `run` passes them: the kind is served here. */
153
+ const SERVED_RUN_OPTIONS = { accountPreferencesAvailable: true } as const;
154
154
 
155
155
  describe("prepareAgentExec env injection", () => {
156
156
  it("injects STIGMER_ORG_ID when absent", async () => {
@@ -272,21 +272,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
272
272
  expect(prepared.harness).toBe("native");
273
273
  });
274
274
 
275
- it("ignores the preference when the caller has not opted in (draft, D5)", async () => {
276
- // draft runs the seedpack's system creator agents — a utility flow that
277
- // must not be silently rerouted onto the cursor engine by a preference
278
- // meant for the user's own sessions (owner-ratified D5).
279
- const prepared = await prepareAgentExec(
280
- BASE_FLAGS,
281
- clientWithPreferences({ defaultHarness: "cursor" }),
282
- "acme",
283
- undefined,
284
- { accountPreferencesAvailable: true },
285
- );
286
- expect(prepared.harness).toBe("");
287
- });
288
-
289
- it("an explicit --harness still resolves for a non-opted-in caller (draft escape hatch)", async () => {
275
+ it("an explicit --harness resolves as given when the server serves no account preferences", async () => {
290
276
  const prepared = await prepareAgentExec(
291
277
  { ...BASE_FLAGS, harness: "cursor" },
292
278
  STUB_CLIENT,
@@ -329,7 +315,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
329
315
  clientWithFailingWhoAmI(),
330
316
  "acme",
331
317
  undefined,
332
- { accountPreferencesAvailable: false, applyAccountHarnessDefault: true },
318
+ { accountPreferencesAvailable: false },
333
319
  );
334
320
  expect(prepared.harness).toBe("");
335
321
  });
@@ -41,7 +41,7 @@ export type ThinkingFlag = "" | "disabled" | "enabled";
41
41
  */
42
42
  export type HarnessFlag = "" | "native" | "cursor";
43
43
 
44
- /** Raw agent-execution flags shared by `run` and `draft` (Go's agentExecFlags). */
44
+ /** Raw agent-execution flags as `run` parses them (Go's agentExecFlags). */
45
45
  export interface AgentExecFlags {
46
46
  readonly message: string;
47
47
  readonly attach: readonly string[];
@@ -93,28 +93,17 @@ export interface PreparedRun {
93
93
  /** Optional behavior switches for {@link prepareAgentExec}. */
94
94
  export interface PrepareAgentExecOptions {
95
95
  /**
96
- * When `true` and `--model` is omitted, the model is filled from the
97
- * caller's account preference (`IdentityAccountPreferences.default_*_model`
98
- * for the resolved harness) via `whoAmI()`. The caller answers this from
99
- * the connected server's edition and the kind's tier —
96
+ * When `true`, an omitted `--harness` is filled from the caller's account
97
+ * preference (`IdentityAccountPreferences.default_harness`) and an omitted
98
+ * `--model` from `default_*_model` for the resolved harness, both via one
99
+ * `whoAmI()`. The caller answers this from the connected server's edition
100
+ * and the kind's tier —
100
101
  * `client.isResourceAvailable(ApiResourceKind.identity_account)` — never
101
102
  * from the config's backend type (20260911.11 A3): a server that does not
102
- * serve identity accounts is not even asked, and the omitted model keeps
103
- * resolving to the platform default.
103
+ * serve identity accounts is not even asked, and the omitted values keep
104
+ * resolving to the platform defaults.
104
105
  */
105
106
  readonly accountPreferencesAvailable?: boolean;
106
- /**
107
- * When `true` and `--harness` is omitted, the harness is filled from the
108
- * caller's account preference (`IdentityAccountPreferences.default_harness`)
109
- * where the kind is served. `run` opts in; `draft` deliberately does not: draft executes the
110
- * seedpack's system creator agents (a utility flow, not the user's own
111
- * conversation), which are built and exercised on the native harness —
112
- * silently rerouting them onto the cursor engine (different toolset,
113
- * premium billing tier) would fail the least-surprise test. An explicit
114
- * `--harness` on draft still wins; only the silent preference fill is
115
- * scoped out. Owner-ratified (D5, 2026-08-23).
116
- */
117
- readonly applyAccountHarnessDefault?: boolean;
118
107
  }
119
108
 
120
109
  /**
@@ -137,13 +126,12 @@ export async function prepareAgentExec(
137
126
  // Layered seeds (oss#293, DD-003): explicit flag > account preference
138
127
  // (where the server serves identity accounts) > platform default. Harness resolves first because the model
139
128
  // fill is harness-aware: a cursor session fills from default_cursor_model,
140
- // everything else from default_native_model. `run` and `draft` always
141
- // create a NEW session (threading lives in `resume`, which does not pass
142
- // through here), so neither fill can ever contradict an existing session's
129
+ // everything else from default_native_model. `run` always creates a NEW
130
+ // session (threading lives in `resume`, which does not pass through
131
+ // here), so neither fill can ever contradict an existing session's
143
132
  // immutable harness. One whoAmI round trip serves both fills, and it is
144
133
  // skipped entirely when explicit flags leave nothing to fill.
145
- const wantsHarnessFill =
146
- flags.harness === "" && options?.applyAccountHarnessDefault === true;
134
+ const wantsHarnessFill = flags.harness === "";
147
135
  const wantsModelFill = flags.model === "";
148
136
  const accountDefaults =
149
137
  options?.accountPreferencesAvailable === true &&
@@ -15,6 +15,7 @@ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apires
15
15
  import { UpdateVisibilityInputSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
16
16
  import type { Stigmer } from "@stigmer/sdk";
17
17
  import { strFromU8, unzipSync, zipSync } from "fflate";
18
+ import { DETERMINISTIC_ZIP_MTIME } from "@stigmer/plugin-package/client";
18
19
  import { parse as parseYaml } from "yaml";
19
20
  import { UsageError } from "../errors/index.js";
20
21
  import { getGitBranchName, getGitCommit, getGitRemoteUrl, getGitRepoRoot } from "./git.js";
@@ -22,16 +23,11 @@ import { createMatcher, REASON_TEXT, type Reason } from "./ignore/index.js";
22
23
 
23
24
  export const SKILL_FILE = "SKILL.md";
24
25
 
25
- // A skill version's identity is the server-side SHA-256 of the uploaded zip
26
- // bytes, so packaging must be a pure function of content — otherwise
27
- // re-pushing unchanged content registers a new version and the server's
28
- // unchanged-content no-op never fires (stigmer/stigmer#671). fflate stamps
29
- // zip-creation time into every entry when no mtime is given; pinning the DOS
30
- // epoch (the earliest representable zip timestamp) removes the only
31
- // byte-level variance. Local-field Date construction is deliberate: DOS
32
- // timestamps store wall-clock fields, so this encodes identically in every
33
- // timezone.
34
- export const DETERMINISTIC_ZIP_MTIME = new Date(1980, 0, 1);
26
+ // The one mtime every archive entry carries is the shared module's
27
+ // `DETERMINISTIC_ZIP_MTIME` (stigmer/stigmer#671: fflate would otherwise
28
+ // stamp creation time into every entry and the server's unchanged-content
29
+ // no-op would never fire). Re-exported so the CLI's callers keep one name.
30
+ export { DETERMINISTIC_ZIP_MTIME };
35
31
  // Kebab-case, optionally scoped with dot-separated namespaces (e.g.
36
32
  // "platform.planton-architecture"). Every segment must be alphanumeric, so no
37
33
  // leading/trailing/consecutive separators. The derived slug renders dots as hyphens.
@@ -1,3 +0,0 @@
1
- import type { Command } from "commander";
2
- export declare function registerDraft(program: Command): void;
3
- //# sourceMappingURL=draft.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../../src/commands/draft.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmDzC,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgBpD"}
package/commands/draft.js DELETED
@@ -1,115 +0,0 @@
1
- // `stigmer draft <skill|agent|mcp-server>` — AI-assisted resource creation. Each
2
- // subcommand invokes a system agent (skill-creator/agent-creator/mcp-server-
3
- // creator) that lives in the "stigmer" org, reusing the full run stack. Mirrors
4
- // Go's draft.go + draft_handler.go.
5
- //
6
- // Draft differs from run only at the edges: the agent is fixed (a system agent),
7
- // --message is required, and artifacts download to -o/--output (or the CWD)
8
- // unless detached or a local workspace is in play.
9
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
10
- import { ensureAuthenticated, resolveOrganization } from "../config/index.js";
11
- import { UsageError } from "../errors/index.js";
12
- import { addAgentExecFlags, toAgentExecFlags, } from "./agent-exec-flags.js";
13
- import { globalOrg } from "./shared.js";
14
- // The org that owns the system agent blueprints (installed by the seedpack),
15
- // regardless of which org the user operates in. Mirrors Go's systemAgentOrg.
16
- const SYSTEM_AGENT_ORG = "stigmer";
17
- const CONFIGS = [
18
- {
19
- name: "skill",
20
- resourceType: "Skill",
21
- summary: "create a skill with AI assistance",
22
- },
23
- {
24
- name: "agent",
25
- resourceType: "Agent",
26
- summary: "create an agent with AI assistance",
27
- },
28
- {
29
- name: "mcp-server",
30
- resourceType: "McpServer",
31
- summary: "create an MCP server config with AI assistance",
32
- },
33
- ];
34
- // System agent slug per subcommand (Go's draftConfig.AgentName: "<type>-creator").
35
- function agentSlug(name) {
36
- return `${name}-creator`;
37
- }
38
- export function registerDraft(program) {
39
- const draft = program
40
- .command("draft")
41
- .description("create resource configurations with AI assistance");
42
- for (const config of CONFIGS) {
43
- const sub = draft.command(config.name).description(config.summary);
44
- addAgentExecFlags(sub, true)
45
- .option("-o, --output <dir>", "directory to save generated artifacts (default: current directory)")
46
- .option("--json", "stream events as newline-delimited JSON")
47
- .action((options, command) => runDraft(config, options, command));
48
- }
49
- }
50
- async function runDraft(config, options, command) {
51
- const { connectBackend } = await import("../backend.js");
52
- const client = connectBackend();
53
- ensureAuthenticated(client.config);
54
- const org = resolveOrganization(client.config, globalOrg(command));
55
- if (org === "") {
56
- throw new UsageError("organization not set\n\nSet it with:\n stigmer config context set --org <org>\n stigmer draft --org <org> ...");
57
- }
58
- const [{ prepareAgentExec }, { executeResolvedAgent }, { resolveAgentRef }, { localWorkspaceRoots },] = await Promise.all([
59
- import("../resources/run/prepare.js"),
60
- import("../resources/run/agent-exec.js"),
61
- import("../resources/run/resolve.js"),
62
- import("../resources/run/workspace.js"),
63
- ]);
64
- const agent = await resolveDraftAgent(resolveAgentRef, client.stigmer, config);
65
- const prepared = await prepareAgentExec(toAgentExecFlags(options), client.stigmer, org, stderrProgress(), {
66
- // The kind's tier against the server's reported edition, as `run` asks it.
67
- accountPreferencesAvailable: await client.isResourceAvailable(ApiResourceKind.identity_account),
68
- });
69
- if (prepared.attachments.length + prepared.workspaceFileRefs.length > 0) {
70
- process.stderr.write(`Attached ${prepared.attachments.length + prepared.workspaceFileRefs.length} file(s) as context\n`);
71
- }
72
- await executeResolvedAgent({
73
- agent,
74
- prepared,
75
- org,
76
- downloadDir: resolveDownloadDir(options.output ?? "", prepared.detach, localWorkspaceRoots(prepared.workspaceEntries)),
77
- outputMode: options.json === true ? "json" : "inline",
78
- client,
79
- });
80
- }
81
- // Resolve the system agent in the "stigmer" org, with seedpack-recovery guidance
82
- // on failure (Go's displayDraftAgentNotFoundError).
83
- async function resolveDraftAgent(resolveAgentRef, client, config) {
84
- const slug = agentSlug(config.name);
85
- try {
86
- return await resolveAgentRef(client, `${SYSTEM_AGENT_ORG}/${slug}`, SYSTEM_AGENT_ORG);
87
- }
88
- catch {
89
- throw new UsageError(`${slug} agent not found in organization "${SYSTEM_AGENT_ORG}"\n\n` +
90
- `System agents are installed by the seedpack in the "${SYSTEM_AGENT_ORG}" organization.\n\n` +
91
- "Troubleshooting:\n" +
92
- ` 1. Verify agents exist: stigmer list agents --org ${SYSTEM_AGENT_ORG}\n` +
93
- " 2. Re-apply seedpack: stigmer apply -f seedpack/");
94
- }
95
- }
96
- // Download-target resolution (Go's executeDraft switch):
97
- // detach → no download (execution is backgrounded)
98
- // explicit -o → that directory
99
- // local workspace → skip (the agent writes directly to disk)
100
- // otherwise → current directory
101
- function resolveDownloadDir(output, detach, localRoots) {
102
- if (detach)
103
- return "";
104
- if (output !== "")
105
- return output;
106
- if (localRoots.length > 0)
107
- return "";
108
- return ".";
109
- }
110
- function stderrProgress() {
111
- if (process.stderr.isTTY !== true)
112
- return () => { };
113
- return (line) => void process.stderr.write(`${line}\n`);
114
- }
115
- //# sourceMappingURL=draft.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"draft.js","sourceRoot":"","sources":["../../src/commands/draft.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export declare const DEFAULT_PATTERNS: readonly string[];
2
- //# sourceMappingURL=defaults.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../../src/resources/ignore/defaults.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAsJ7C,CAAC"}