stitchkit 0.90.2 → 0.90.4

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 (57) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/dist/agent-runtime-coding-tools.js +3 -3
  3. package/dist/agent-runtime-harness.js +4 -4
  4. package/dist/agent-runtime-sandbox.js +3 -3
  5. package/dist/agent-runtime.js +7 -7
  6. package/dist/cli.d.ts +14 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +359 -11
  9. package/dist/{index-gbqjt8jz.js → index-19ryv24q.js} +3 -3
  10. package/dist/{index-2hrfpw2c.js → index-32vjke6q.js} +2 -2
  11. package/dist/{index-f475yj00.js → index-6atvfjc1.js} +1 -1
  12. package/dist/{index-79hb1wyh.js → index-7t0wq6j5.js} +3 -3
  13. package/dist/{index-j7q0xj6d.js → index-8crm1srv.js} +1 -1
  14. package/dist/{index-kc6h6hg0.js → index-8z9we758.js} +1 -1
  15. package/dist/{index-r159gjwy.js → index-fenaekmk.js} +94 -8
  16. package/dist/{index-wb15909q.js → index-kzxpsf8y.js} +3 -3
  17. package/dist/{index-44ht2790.js → index-vcnfwrtr.js} +1 -1
  18. package/dist/{index-8dph3pw6.js → index-y47z614h.js} +405 -35
  19. package/dist/testing.js +1 -1
  20. package/dist/tool-invoker.js +4 -4
  21. package/dist/tools/cli-args.d.ts +49 -0
  22. package/dist/tools/cli-args.d.ts.map +1 -1
  23. package/dist/tools/cli-command.d.ts +8 -1
  24. package/dist/tools/cli-command.d.ts.map +1 -1
  25. package/dist/tools/cli-installer.d.ts +29 -0
  26. package/dist/tools/cli-installer.d.ts.map +1 -0
  27. package/dist/tools/cli-manifest.d.ts +83 -0
  28. package/dist/tools/cli-manifest.d.ts.map +1 -0
  29. package/dist/tools/cli-policy.d.ts +6 -1
  30. package/dist/tools/cli-policy.d.ts.map +1 -1
  31. package/dist/tools/cli-profile.d.ts +40 -0
  32. package/dist/tools/cli-profile.d.ts.map +1 -0
  33. package/dist/tools/cli-update.d.ts +64 -0
  34. package/dist/tools/cli-update.d.ts.map +1 -0
  35. package/dist/tools/cli-view.d.ts +12 -0
  36. package/dist/tools/cli-view.d.ts.map +1 -0
  37. package/dist/tools/cli.d.ts +20 -5
  38. package/dist/tools/cli.d.ts.map +1 -1
  39. package/dist/tools/connections/index.d.ts +2 -1
  40. package/dist/tools/connections/index.d.ts.map +1 -1
  41. package/dist/tools/connections/index.js +37 -10
  42. package/dist/tools/connections/mcp.d.ts +2 -1
  43. package/dist/tools/connections/mcp.d.ts.map +1 -1
  44. package/dist/tools/connections/mount.d.ts.map +1 -1
  45. package/dist/tools/connections/openapi.d.ts +2 -1
  46. package/dist/tools/connections/openapi.d.ts.map +1 -1
  47. package/dist/tools/connections/runtime.d.ts +17 -0
  48. package/dist/tools/connections/runtime.d.ts.map +1 -1
  49. package/dist/tools/connections/types.d.ts +18 -1
  50. package/dist/tools/connections/types.d.ts.map +1 -1
  51. package/dist/tools/json-schema-dialect.d.ts +17 -0
  52. package/dist/tools/json-schema-dialect.d.ts.map +1 -0
  53. package/dist/tools/presentation.d.ts +8 -1
  54. package/dist/tools/presentation.d.ts.map +1 -1
  55. package/dist/tools.js +8 -8
  56. package/llms-full.txt +293 -7
  57. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -15,6 +15,135 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.90.4] — 2026-09-16
19
+
20
+ ### Added
21
+
22
+ - **Aggregate views on the CLI: `--count-by`, `--sum`, `--top`, `--table`.** The
23
+ CLI's audience is agents and scripts, which is why output is JSON — but
24
+ answering "how many per status" by shipping the whole collection is a separate
25
+ decision, and an expensive one. Measured on a live server: a 98-record listing
26
+ is 34 750 characters of an agent's context window; `--count-by status` is about
27
+ ninety. `| jq` cannot recover that, because the bytes have already been read
28
+ into the conversation by the time the pipe sees them. `--by` names the grouping
29
+ field in every form it appears in; groups come back largest first, so `--top`
30
+ is a defined slice. A field no record carries is an argument error naming the
31
+ fields that exist, never a silent empty group, and an aggregate needs a
32
+ collection — over a scalar it is refused rather than guessed. Without a view
33
+ flag the emitted bytes are unchanged, and the flags never reach a tool
34
+ argument.
35
+ - **`createCliProfileStore` — named credential profiles that are never
36
+ substituted.** A CLI that talks to more than one deployment picks an
37
+ environment by name, and the convenient implementation of that is the unsafe
38
+ one: *the named profile does not exist, but exactly one is configured — use
39
+ it.* It is correct while a single profile exists and a wrong-environment
40
+ command the day a second appears. So a name given and not found is a refusal
41
+ that names the path and how to create it; substitution survives only where no
42
+ name was given and exactly one profile exists, and it is announced on stderr.
43
+ Files are `0600` in a `0700` directory, and a profile other users can read is
44
+ refused with the `chmod` that fixes it.
45
+ - **CLI distribution and self-update.** A build manifest schema
46
+ (`CliBuildManifestSchema`), an installer generated from it
47
+ (`renderCliInstaller`), a bounded update check (`checkCliUpdate`) and a
48
+ verified atomic replace (`applyCliUpdate`). The installer parses no JSON — a
49
+ `curl … | sh` machine has no `jq`, so the URL and digest are substituted
50
+ server-side. The digest covers the **decompressed** bytes, because those are
51
+ what will be executed. The replace is a rename inside the target's own
52
+ directory, so an interrupted download can never leave a half-written
53
+ executable on a PATH. `assertCliPublishable` refuses republishing one version
54
+ from a different commit, and the check has four answers — `skipped`,
55
+ `current`, `outdated`, `unknown` — because "could not ask" is not "up to
56
+ date". Checking never replaces anything by itself. → ADR 0186
57
+ - **`transports` on a connection — discovered tools as CLI commands.**
58
+ `mountConnections` and `createCli` already composed into a CLI whose surface is
59
+ the one the server has right now rather than the one the binary was compiled
60
+ against, except that discovered tools were filtered out of the CLI projection
61
+ with no declarative way in. Naming `transports: ['CLI']` on a connection makes
62
+ that server's tools commands; a connection without it still contributes
63
+ nothing, so exposure stays explicit.
64
+ - **`coerceJsonArgs`, `routeCliArgv`, `extractCliGlobalOptions` and
65
+ `CliArgumentError` are exported from `stitchkit/cli`.** `parseCliArgs`
66
+ deliberately leaves array and object values as strings for the pass that runs
67
+ next; a consumer that parses argv and sends the call itself needs both halves,
68
+ and reaching for the `stitchkit/tools` barrel to get one function pulls the MCP
69
+ and AI peers into a binary this entrypoint is careful to avoid.
70
+
71
+ ### Fixed
72
+
73
+ - **A stitchkit MCP surface is readable by the stitchkit MCP client again.**
74
+ `mountConnections` threw `Reference not found:
75
+ #/definitions/input/definitions/__schema0` against a server that is itself
76
+ built on stitchkit, and on one real surface of 205 tools five carried such a
77
+ reference — enough to lose the whole connection, because the mount was
78
+ all-or-nothing. Two defects underneath. The served document declared the
79
+ 2020-12 dialect (the SDK stamps it) while using draft-07's `definitions`
80
+ keyword, so any reader that registers subschemas from the keyword its dialect
81
+ names found nothing to resolve against; MCP metadata now moves to `$defs` and
82
+ carries its pointers with it. And definitions were nested one level deeper than
83
+ a reader looks, because namespacing wrapped each schema instead of prefixing
84
+ its definition names; they are hoisted to the document root now, `allOf`
85
+ merges included. The client also no longer believes a contradicted stamp, which
86
+ is how it reaches every stitchkit server published before this release. → ADR 0185
87
+ - **One unconvertible discovered tool no longer takes the connection down.**
88
+ `mountConnections` builds per tool, skips what it cannot build and names it —
89
+ through `onSkippedTool`, or a stderr line naming the connection, the tool and
90
+ the reason.
91
+
92
+ ## [0.90.3] — 2026-09-15
93
+
94
+ ### Added
95
+
96
+ - **A trailing positional may be a list.** When the last field of a command's
97
+ declared `positionals` is an array, it takes every remaining argv token,
98
+ coerced by the element type: `myapp handoff proj a.md b.md` instead of
99
+ `--files '["a.md","b.md"]'`. One token is a one-element list, so the parsed
100
+ shape never depends on how many a caller happened to pass, and command help
101
+ says which field is the list (`<to> <files...>`). The flag form still works;
102
+ passing both forms in one call is an argument error rather than a silent
103
+ merge. Only the trailing position is variadic — an array declared earlier
104
+ keeps taking exactly one JSON token, unchanged.
105
+ - **`globalOptions` — the application's own invocation context.** A CLI's
106
+ globals were only ever the framework's (`--json`, `--wait`, …), so an app that
107
+ needed `--caller <key>` or `--root <dir>` had to read an environment variable
108
+ or parse argv itself, next to the parser it already had. Declare them as a Zod
109
+ object and `createCli` lifts them out of argv wherever they stand — before or
110
+ after the command name — validates them, keeps them out of every operation's
111
+ arguments, and hands them to `resolveAuth(globals)`, `context(auth, globals)`
112
+ and a native command's `globals`. A name that collides with a framework option
113
+ or with a field of any command is refused at startup instead of shadowing it
114
+ silently, and both help levels list them under `Application options:`.
115
+
116
+ ### Fixed
117
+
118
+ - **Output past the pipe buffer is no longer lost to a slow reader.** The
119
+ default writer called `writeSync` and ignored the byte count it returns. Once
120
+ anything in the process has touched `process.stdout` the descriptor is
121
+ non-blocking, so a write into a pipe whose reader has not started yet stores
122
+ what the 64 KB buffer takes, returns that count and throws nothing: the tail
123
+ vanished with exit code `0`, at exactly 65536 bytes — the same number as the
124
+ truncation the synchronous writer had been introduced to fix, which is why it
125
+ read as already fixed for so long. A consuming CLI saw `… | jq` fail with
126
+ "Unfinished string at column 65536" while the same command redirected to a
127
+ file wrote the whole result. The writer now loops on the returned count and
128
+ treats a full buffer (`EAGAIN`) as a wait rather than a failure, which is what
129
+ a blocking write into an undrained pipe is supposed to do; where an exotic
130
+ descriptor still forces the async fallback, the default `exit` waits for that
131
+ write instead of cutting it off, and output that could not be delivered at all
132
+ now exits non-zero.
133
+ - **Help survives a managed surface that cannot resolve.** A CLI whose commands
134
+ come from a running server declares them with a factory, and the factory needs
135
+ an identity. When `resolveAuth` failed — server down, key file missing — the
136
+ rejection escaped `createCli` and `--help` printed *nothing*: not the native
137
+ commands, which never depended on identity, and not the reason. Calling such a
138
+ name was worse than silence, because the surface never resolved and the answer
139
+ would have been `Unknown command` — a claim that the name does not exist when
140
+ the truth is that it could not be looked up. Top-level help now lists the
141
+ native commands and one line naming the refusal (`Managed commands are
142
+ unavailable: UNREACHABLE: socket closed`), and an unresolvable name answers
143
+ with that refusal and the exit code its error class declares through
144
+ `exitCodes`. Identity is still resolved at most once per invocation, failure
145
+ included.
146
+
18
147
  ## [0.90.2] — 2026-09-15
19
148
 
20
149
  ### Fixed
@@ -3,15 +3,15 @@ import {
3
3
  AgentCodingToolLimitsSchema,
4
4
  AgentCodingToolPathAuthorizationSchema,
5
5
  createAgentCodingTools
6
- } from "./index-j7q0xj6d.js";
6
+ } from "./index-8crm1srv.js";
7
7
  import"./index-bn2cjajt.js";
8
8
  import"./index-gte38nbm.js";
9
9
  import"./index-kazec06k.js";
10
10
  import"./index-6k1937bx.js";
11
11
  import"./index-scs3f1eg.js";
12
12
  import"./index-kzfs85xp.js";
13
- import"./index-kc6h6hg0.js";
14
- import"./index-r159gjwy.js";
13
+ import"./index-8z9we758.js";
14
+ import"./index-fenaekmk.js";
15
15
  import"./index-cby4ar3v.js";
16
16
  import"./index-77fekveh.js";
17
17
  export {
@@ -6,7 +6,7 @@ import {
6
6
  composeAgentPrompt,
7
7
  createAgentRuntime,
8
8
  createAgentRuntimeEventSink
9
- } from "./index-f475yj00.js";
9
+ } from "./index-6atvfjc1.js";
10
10
  import"./index-p13mwz16.js";
11
11
  import"./index-y64k3s0h.js";
12
12
  import {
@@ -19,7 +19,7 @@ import {
19
19
  walkContainedFiles
20
20
  } from "./index-bn2cjajt.js";
21
21
  import"./index-7rkhw9ec.js";
22
- import"./index-44ht2790.js";
22
+ import"./index-vcnfwrtr.js";
23
23
  import"./index-vsbzgd7b.js";
24
24
  import"./index-f9mb610r.js";
25
25
  import"./index-2hryh65w.js";
@@ -31,8 +31,8 @@ import"./index-7zbps32p.js";
31
31
  import"./index-kzfs85xp.js";
32
32
  import {
33
33
  defineRuntimeTool
34
- } from "./index-kc6h6hg0.js";
35
- import"./index-r159gjwy.js";
34
+ } from "./index-8z9we758.js";
35
+ import"./index-fenaekmk.js";
36
36
  import"./index-cby4ar3v.js";
37
37
  import"./index-77fekveh.js";
38
38
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createAgentCodingTools
3
- } from "./index-j7q0xj6d.js";
3
+ } from "./index-8crm1srv.js";
4
4
  import"./index-bn2cjajt.js";
5
5
  import {
6
6
  codingRefusal,
@@ -11,8 +11,8 @@ import"./index-kazec06k.js";
11
11
  import"./index-6k1937bx.js";
12
12
  import"./index-scs3f1eg.js";
13
13
  import"./index-kzfs85xp.js";
14
- import"./index-kc6h6hg0.js";
15
- import"./index-r159gjwy.js";
14
+ import"./index-8z9we758.js";
15
+ import"./index-fenaekmk.js";
16
16
  import"./index-cby4ar3v.js";
17
17
  import"./index-77fekveh.js";
18
18
 
@@ -31,7 +31,7 @@ import {
31
31
  renderAgentStateSlots,
32
32
  repairedSearchCall,
33
33
  selectAgentHistory
34
- } from "./index-f475yj00.js";
34
+ } from "./index-6atvfjc1.js";
35
35
  import"./index-p13mwz16.js";
36
36
  import {
37
37
  AgentAdmissionEventSchema,
@@ -169,11 +169,11 @@ import {
169
169
  import {
170
170
  buildToolManifest,
171
171
  mountAgent
172
- } from "./index-gbqjt8jz.js";
172
+ } from "./index-19ryv24q.js";
173
173
  import"./index-7rkhw9ec.js";
174
- import"./index-wb15909q.js";
175
- import"./index-2hrfpw2c.js";
176
- import"./index-44ht2790.js";
174
+ import"./index-kzxpsf8y.js";
175
+ import"./index-32vjke6q.js";
176
+ import"./index-vcnfwrtr.js";
177
177
  import"./index-vsbzgd7b.js";
178
178
  import"./index-f9mb610r.js";
179
179
  import"./index-2hryh65w.js";
@@ -188,8 +188,8 @@ import"./index-7zbps32p.js";
188
188
  import"./index-kzfs85xp.js";
189
189
  import {
190
190
  defineRuntimeTool
191
- } from "./index-kc6h6hg0.js";
192
- import"./index-r159gjwy.js";
191
+ } from "./index-8z9we758.js";
192
+ import"./index-fenaekmk.js";
193
193
  import"./index-cby4ar3v.js";
194
194
  import {
195
195
  executableAgentRuntimeTools
package/dist/cli.d.ts CHANGED
@@ -17,9 +17,22 @@
17
17
  * and point your app's `package.json` `bin` at it.
18
18
  */
19
19
  export { type CliConfig, type CliSurfaceSource, createCli } from './tools/cli.js';
20
- export { type CliRunOptions, type ParsedCliArgs, parseCliArgs } from './tools/cli-args.js';
20
+ export { CliArgumentError, type CliArgvRoute, type CliGlobalOptionsParse, type CliResultView, type CliRunOptions, extractCliGlobalOptions, type ParsedCliArgs, parseCliArgs, routeCliArgv, } from './tools/cli-args.js';
21
21
  export { type CliCommandContext, type CliCommandDefinition, type CliCommandDefinitionBase, type CliCommandDefinitionWithOutput, type CliCommandDefinitionWithoutOutput, defineCliCommand, } from './tools/cli-command.js';
22
22
  export { type CliWriters, DEFAULT_EXIT_CODES, type EmitOptions, type ExitCodeMap, emitResult, } from './tools/cli-format.js';
23
+ export { type CliInstallerConfig, renderCliInstaller, } from './tools/cli-installer.js';
24
+ export { assertCliPublishable, type CliBuildAsset, CliBuildAssetSchema, type CliBuildManifest, CliBuildManifestSchema, type CliBuildStamp, CliBuildStampSchema, type CliBuildTarget, CliBuildTargetSchema, currentCliBuildTarget, formatCliBuildStamp, selectCliBuildAsset, } from './tools/cli-manifest.js';
23
25
  export type { CliPresentationPolicyConfig } from './tools/cli-policy.js';
26
+ export { CliProfileError, type CliProfileStore, type CliProfileStoreConfig, createCliProfileStore, type ResolvedCliProfile, } from './tools/cli-profile.js';
27
+ export { type AppliedCliUpdate, applyCliUpdate, type CliUpdateApplyConfig, type CliUpdateCheck, type CliUpdateCheckConfig, checkCliUpdate, compareCliVersions, } from './tools/cli-update.js';
28
+ export { type CliViewOutput, renderCliView } from './tools/cli-view.js';
24
29
  export { type CliWaitConfig, type PollParams, pollUntilDone } from './tools/cli-wait.js';
30
+ /**
31
+ * The second half of `parseCliArgs`. The parser leaves array and object values
32
+ * as strings on purpose, because `executeToolMethod` runs this pass next; a
33
+ * consumer that parses argv and then sends the call itself needs both halves,
34
+ * and reaching for the `stitchkit/tools` barrel to get one function would pull
35
+ * the MCP and AI peers into a binary this entrypoint keeps free of them.
36
+ */
37
+ export { coerceJsonArgs } from './tools/coerce.js';
25
38
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACxF,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACnC,KAAK,iCAAiC,EACtC,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,UAAU,EACf,kBAAkB,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,UAAU,GACX,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EACL,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,uBAAuB,EACvB,KAAK,aAAa,EAClB,YAAY,EACZ,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACnC,KAAK,iCAAiC,EACtC,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,UAAU,EACf,kBAAkB,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,UAAU,GACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,kBAAkB,EACvB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EACpB,KAAK,aAAa,EAClB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,KAAK,aAAa,EAClB,mBAAmB,EACnB,KAAK,cAAc,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EACL,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,qBAAqB,EACrB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,gBAAgB,EACrB,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,cAAc,EACd,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,aAAa,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtF;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/cli.js CHANGED
@@ -1,33 +1,381 @@
1
1
  import {
2
+ CliArgumentError,
2
3
  DEFAULT_EXIT_CODES,
3
4
  createCli,
4
5
  defineCliCommand,
5
6
  emitResult,
7
+ extractCliGlobalOptions,
8
+ fetchGuarded,
6
9
  parseCliArgs,
7
- pollUntilDone
8
- } from "./index-8dph3pw6.js";
10
+ pollUntilDone,
11
+ readCapped,
12
+ renderCliView,
13
+ routeCliArgv
14
+ } from "./index-y47z614h.js";
9
15
  import"./index-sbdmyz75.js";
10
- import"./index-wb15909q.js";
11
- import"./index-2hrfpw2c.js";
12
- import"./index-44ht2790.js";
16
+ import"./index-kzxpsf8y.js";
17
+ import"./index-32vjke6q.js";
18
+ import"./index-vcnfwrtr.js";
13
19
  import"./index-vsbzgd7b.js";
14
20
  import"./index-f9mb610r.js";
15
21
  import"./index-2hryh65w.js";
16
22
  import"./index-kazec06k.js";
17
23
  import"./index-6k1937bx.js";
18
- import"./index-scs3f1eg.js";
24
+ import {
25
+ AppError
26
+ } from "./index-scs3f1eg.js";
19
27
  import"./index-zcgf3gqf.js";
20
- import"./index-7zbps32p.js";
21
- import"./index-kzfs85xp.js";
22
- import"./index-kc6h6hg0.js";
23
- import"./index-r159gjwy.js";
28
+ import {
29
+ coerceJsonArgs
30
+ } from "./index-7zbps32p.js";
31
+ import {
32
+ safeJsonParse
33
+ } from "./index-kzfs85xp.js";
34
+ import"./index-8z9we758.js";
35
+ import"./index-fenaekmk.js";
24
36
  import"./index-cby4ar3v.js";
25
37
  import"./index-77fekveh.js";
38
+ // src/tools/cli-installer.ts
39
+ function shellQuote(value) {
40
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
41
+ }
42
+ function renderCliInstaller(config) {
43
+ const binary = config.binaryName ?? config.manifest.name;
44
+ const installDir = config.installDir ?? "$HOME/.local/bin";
45
+ const decompress = config.asset.compression === "gzip" ? ` gzip -dc "$tmp/download" > "$tmp/binary"
46
+ ` : ` mv "$tmp/download" "$tmp/binary"
47
+ `;
48
+ return `#!/bin/sh
49
+ # ${binary} ${config.manifest.version} (${config.manifest.commit})
50
+ # Generated from the build manifest — it parses no JSON and needs no jq.
51
+ set -eu
52
+
53
+ url=${shellQuote(config.asset.url)}
54
+ sha256=${shellQuote(config.asset.sha256)}
55
+ size=${shellQuote(String(config.asset.size))}
56
+ binary=${shellQuote(binary)}
57
+ dir="\${INSTALL_DIR:-${installDir}}"
58
+
59
+ fetch() {
60
+ if command -v curl >/dev/null 2>&1; then
61
+ curl -fsSL "$1" -o "$2"
62
+ elif command -v wget >/dev/null 2>&1; then
63
+ wget -q "$1" -O "$2"
64
+ else
65
+ echo "need curl or wget" >&2
66
+ exit 1
67
+ fi
68
+ }
69
+
70
+ digest() {
71
+ if command -v sha256sum >/dev/null 2>&1; then
72
+ sha256sum "$1" | cut -d' ' -f1
73
+ elif command -v shasum >/dev/null 2>&1; then
74
+ shasum -a 256 "$1" | cut -d' ' -f1
75
+ elif command -v openssl >/dev/null 2>&1; then
76
+ openssl dgst -sha256 "$1" | awk '{print $NF}'
77
+ else
78
+ echo "need sha256sum, shasum or openssl" >&2
79
+ exit 1
80
+ fi
81
+ }
82
+
83
+ tmp="$(mktemp -d)"
84
+ trap 'rm -rf "$tmp"' EXIT
85
+
86
+ fetch "$url" "$tmp/download"
87
+ ${decompress}
88
+ actual="$(digest "$tmp/binary")"
89
+ if [ "$actual" != "$sha256" ]; then
90
+ echo "checksum mismatch: expected $sha256, got $actual" >&2
91
+ exit 1
92
+ fi
93
+
94
+ actual_size="$(wc -c < "$tmp/binary" | tr -d ' ')"
95
+ if [ "$actual_size" != "$size" ]; then
96
+ echo "size mismatch: expected $size bytes, got $actual_size" >&2
97
+ exit 1
98
+ fi
99
+
100
+ mkdir -p "$dir"
101
+ chmod 755 "$tmp/binary"
102
+ # A rename, never a write over the file in place: anything else can leave a
103
+ # half-written executable where a working one used to be.
104
+ mv "$tmp/binary" "$dir/$binary"
105
+
106
+ echo "installed $binary ${config.manifest.version} to $dir/$binary"
107
+ case ":$PATH:" in
108
+ *":$dir:"*) ;;
109
+ *) echo "note: $dir is not on your PATH" >&2 ;;
110
+ esac
111
+ `;
112
+ }
113
+ // src/tools/cli-manifest.ts
114
+ import { z } from "zod";
115
+ var CliBuildTargetSchema = z.object({
116
+ platform: z.string().min(1),
117
+ arch: z.string().min(1)
118
+ });
119
+ var CliBuildAssetSchema = CliBuildTargetSchema.extend({
120
+ url: z.url(),
121
+ compression: z.enum(["gzip", "none"]).default("none"),
122
+ size: z.int().positive(),
123
+ sha256: z.string().regex(/^[0-9a-f]{64}$/, "sha256 must be 64 lowercase hex characters")
124
+ });
125
+ var CliBuildManifestSchema = z.object({
126
+ name: z.string().min(1),
127
+ version: z.string().min(1),
128
+ commit: z.string().min(1),
129
+ builtAt: z.iso.datetime(),
130
+ assets: z.array(CliBuildAssetSchema)
131
+ });
132
+ var CliBuildStampSchema = z.object({
133
+ version: z.string().min(1),
134
+ commit: z.string().min(1),
135
+ builtAt: z.iso.datetime()
136
+ });
137
+ function formatCliBuildStamp(stamp, name) {
138
+ const prefix = name ? `${name} ` : "";
139
+ return `${prefix}${stamp.version} (${stamp.commit.slice(0, 12)}, built ${stamp.builtAt})`;
140
+ }
141
+ function currentCliBuildTarget() {
142
+ return { platform: process.platform, arch: process.arch };
143
+ }
144
+ function selectCliBuildAsset(manifest, target = currentCliBuildTarget()) {
145
+ return manifest.assets.find((asset) => asset.platform === target.platform && asset.arch === target.arch);
146
+ }
147
+ function assertCliPublishable(previous, next) {
148
+ if (!previous)
149
+ return;
150
+ if (previous.version !== next.version)
151
+ return;
152
+ if (previous.commit === next.commit)
153
+ return;
154
+ throw new Error(`[stitchkit] ${next.name} ${next.version} was already published from commit ${previous.commit}; ` + `refusing to republish it from ${next.commit} — bump the version instead`);
155
+ }
156
+ // src/tools/cli-profile.ts
157
+ import {
158
+ chmodSync,
159
+ existsSync,
160
+ mkdirSync,
161
+ readdirSync,
162
+ readFileSync,
163
+ statSync,
164
+ writeFileSync
165
+ } from "node:fs";
166
+ import { join } from "node:path";
167
+ var PROFILE_SUFFIX = ".json";
168
+ var FILE_MODE = 384;
169
+ var DIRECTORY_MODE = 448;
170
+
171
+ class CliProfileError extends AppError {
172
+ constructor(message, code = "NOT_FOUND") {
173
+ super(code, message, code === "NOT_FOUND" ? 404 : code === "FORBIDDEN" ? 403 : 400);
174
+ this.name = "CliProfileError";
175
+ }
176
+ }
177
+ function assertProfileName(name) {
178
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
179
+ throw new CliProfileError(`"${name}" is not a usable profile name — letters, digits, dot, dash and underscore`, "BAD_REQUEST");
180
+ }
181
+ }
182
+ function createCliProfileStore(config) {
183
+ const announce = config.announce ?? ((line) => process.stderr.write(`${line}
184
+ `));
185
+ const pathFor = (name) => {
186
+ assertProfileName(name);
187
+ return join(config.directory, `${name}${PROFILE_SUFFIX}`);
188
+ };
189
+ const list = () => {
190
+ if (!existsSync(config.directory))
191
+ return [];
192
+ return readdirSync(config.directory).filter((entry) => entry.endsWith(PROFILE_SUFFIX)).map((entry) => entry.slice(0, -PROFILE_SUFFIX.length)).sort();
193
+ };
194
+ const read = (name) => {
195
+ const path = pathFor(name);
196
+ if (!existsSync(path)) {
197
+ throw new CliProfileError(missingMessage(name, path, config));
198
+ }
199
+ const mode = statSync(path).mode & 63;
200
+ if (mode !== 0) {
201
+ throw new CliProfileError(`profile "${name}" is readable by other users — run: chmod 600 ${path}`, "FORBIDDEN");
202
+ }
203
+ const parsed = safeJsonParse(readFileSync(path, "utf8"));
204
+ const result = config.schema.safeParse(parsed);
205
+ if (!result.success) {
206
+ const issue = result.error.issues[0];
207
+ const field = issue?.path.join(".");
208
+ throw new CliProfileError(`profile "${name}" (${path}) is not usable: ${field ? `${field}: ` : ""}${issue?.message ?? "invalid"}`, "BAD_REQUEST");
209
+ }
210
+ return result.data;
211
+ };
212
+ return {
213
+ directory: config.directory,
214
+ pathFor,
215
+ list,
216
+ read,
217
+ write(name, value) {
218
+ const path = pathFor(name);
219
+ mkdirSync(config.directory, { recursive: true, mode: DIRECTORY_MODE });
220
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
221
+ `, { mode: FILE_MODE });
222
+ chmodSync(path, FILE_MODE);
223
+ return path;
224
+ },
225
+ resolve(name) {
226
+ if (name !== undefined) {
227
+ const path = pathFor(name);
228
+ return { name, path, value: read(name), substituted: false };
229
+ }
230
+ const configured = list();
231
+ const only = configured[0];
232
+ if (configured.length === 1 && only !== undefined) {
233
+ const path = pathFor(only);
234
+ announce(`[${only}] using the only configured profile (${path})`);
235
+ return { name: only, path, value: read(only), substituted: true };
236
+ }
237
+ if (configured.length === 0) {
238
+ const example = join(config.directory, `<name>${PROFILE_SUFFIX}`);
239
+ throw new CliProfileError(`no profile is configured in ${config.directory}${config.createHint ? ` — ${config.createHint("<name>", example)}` : ""}`);
240
+ }
241
+ throw new CliProfileError(`several profiles are configured (${configured.join(", ")}) — name one`, "BAD_REQUEST");
242
+ }
243
+ };
244
+ }
245
+ function missingMessage(name, path, config) {
246
+ const hint = config.createHint ? ` — ${config.createHint(name, path)}` : "";
247
+ return `profile "${name}" is not configured; expected ${path}${hint}`;
248
+ }
249
+ // src/tools/cli-update.ts
250
+ import { createHash } from "node:crypto";
251
+ import { chmodSync as chmodSync2, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
252
+ import { basename, dirname, join as join2 } from "node:path";
253
+ import { gunzipSync } from "node:zlib";
254
+ var DEFAULT_CHECK_TIMEOUT_MS = 2000;
255
+ var DEFAULT_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
256
+ var DEFAULT_DOWNLOAD_TIMEOUT_MS = 120000;
257
+ var MANIFEST_MAX_BYTES = 256 * 1024;
258
+ var DEFAULT_MAX_ASSET_BYTES = 256 * 1024 * 1024;
259
+ function parseVersion(value) {
260
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(value.trim());
261
+ if (!match)
262
+ return;
263
+ const numbers = [match[1], match[2], match[3]].map((part) => Number(part));
264
+ if (numbers.some((part) => !Number.isFinite(part)))
265
+ return;
266
+ return { numbers, prerelease: match[4] };
267
+ }
268
+ function compareCliVersions(left, right) {
269
+ const a = parseVersion(left);
270
+ const b = parseVersion(right);
271
+ if (!a || !b)
272
+ return;
273
+ for (let index = 0;index < 3; index++) {
274
+ const difference = (a.numbers[index] ?? 0) - (b.numbers[index] ?? 0);
275
+ if (difference !== 0)
276
+ return difference < 0 ? -1 : 1;
277
+ }
278
+ if (a.prerelease === b.prerelease)
279
+ return 0;
280
+ if (a.prerelease === undefined)
281
+ return 1;
282
+ if (b.prerelease === undefined)
283
+ return -1;
284
+ return a.prerelease < b.prerelease ? -1 : 1;
285
+ }
286
+ async function checkCliUpdate(config) {
287
+ const now = config.now ?? Date.now();
288
+ const interval = config.intervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
289
+ if (config.lastCheckedAt !== undefined && now - config.lastCheckedAt < interval) {
290
+ return { status: "skipped", nextCheckAt: config.lastCheckedAt + interval };
291
+ }
292
+ try {
293
+ const response = await fetchGuarded(new URL(config.manifestUrl), config.allowPrivateHosts ?? false, {
294
+ timeoutMs: config.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS,
295
+ bodyTimeoutMs: config.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS
296
+ });
297
+ if (!response.ok)
298
+ return { status: "unknown", reason: `manifest responded ${response.status}` };
299
+ const body = await readCapped(response, MANIFEST_MAX_BYTES);
300
+ if (!body)
301
+ return { status: "unknown", reason: "manifest is larger than expected" };
302
+ const parsed = CliBuildManifestSchema.safeParse(JSON.parse(body.toString("utf8")));
303
+ if (!parsed.success)
304
+ return { status: "unknown", reason: "manifest did not validate" };
305
+ const manifest = parsed.data;
306
+ const order = compareCliVersions(config.currentVersion, manifest.version);
307
+ if (order === undefined) {
308
+ return {
309
+ status: "unknown",
310
+ reason: `cannot compare ${config.currentVersion} with ${manifest.version}`
311
+ };
312
+ }
313
+ if (order >= 0)
314
+ return { status: "current", version: manifest.version };
315
+ const asset = selectCliBuildAsset(manifest, config.target ?? currentCliBuildTarget());
316
+ return {
317
+ status: "outdated",
318
+ version: manifest.version,
319
+ manifest,
320
+ ...asset && { asset }
321
+ };
322
+ } catch (error) {
323
+ return {
324
+ status: "unknown",
325
+ reason: error instanceof Error ? error.message : String(error)
326
+ };
327
+ }
328
+ }
329
+ async function applyCliUpdate(config) {
330
+ const target = config.targetPath ?? process.execPath;
331
+ const response = await fetchGuarded(new URL(config.asset.url), config.allowPrivateHosts ?? false, {
332
+ timeoutMs: config.timeoutMs ?? DEFAULT_DOWNLOAD_TIMEOUT_MS,
333
+ bodyTimeoutMs: config.timeoutMs ?? DEFAULT_DOWNLOAD_TIMEOUT_MS
334
+ });
335
+ if (!response.ok) {
336
+ throw new Error(`[stitchkit] update download responded ${response.status}`);
337
+ }
338
+ const transferred = await readCapped(response, config.maxBytes ?? DEFAULT_MAX_ASSET_BYTES);
339
+ if (!transferred)
340
+ throw new Error("[stitchkit] update download exceeded the size ceiling");
341
+ const bytes = config.asset.compression === "gzip" ? gunzipSync(transferred) : transferred;
342
+ if (bytes.length !== config.asset.size) {
343
+ throw new Error(`[stitchkit] update is ${bytes.length} bytes, manifest says ${config.asset.size} — refusing to install it`);
344
+ }
345
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
346
+ if (sha256 !== config.asset.sha256) {
347
+ throw new Error(`[stitchkit] update digest ${sha256} does not match the manifest — refusing to install it`);
348
+ }
349
+ const staged = join2(dirname(target), `.${basename(target)}.${process.pid}.${Date.now()}`);
350
+ writeFileSync2(staged, bytes, { mode: 493 });
351
+ chmodSync2(staged, 493);
352
+ renameSync(staged, target);
353
+ return { path: target, bytes: bytes.length, sha256 };
354
+ }
26
355
  export {
356
+ selectCliBuildAsset,
357
+ routeCliArgv,
358
+ renderCliView,
359
+ renderCliInstaller,
27
360
  pollUntilDone,
28
361
  parseCliArgs,
362
+ formatCliBuildStamp,
363
+ extractCliGlobalOptions,
29
364
  emitResult,
30
365
  defineCliCommand,
366
+ currentCliBuildTarget,
367
+ createCliProfileStore,
31
368
  createCli,
32
- DEFAULT_EXIT_CODES
369
+ compareCliVersions,
370
+ coerceJsonArgs,
371
+ checkCliUpdate,
372
+ assertCliPublishable,
373
+ applyCliUpdate,
374
+ DEFAULT_EXIT_CODES,
375
+ CliProfileError,
376
+ CliBuildTargetSchema,
377
+ CliBuildStampSchema,
378
+ CliBuildManifestSchema,
379
+ CliBuildAssetSchema,
380
+ CliArgumentError
33
381
  };