cli-jaw 2.2.25 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +5 -2
  2. package/dist/bin/cli-jaw.js +6 -1
  3. package/dist/bin/cli-jaw.js.map +1 -1
  4. package/dist/bin/commands/doctor.js +39 -18
  5. package/dist/bin/commands/doctor.js.map +1 -1
  6. package/dist/bin/commands/init.js +82 -23
  7. package/dist/bin/commands/init.js.map +1 -1
  8. package/dist/bin/commands/messaging.js +262 -0
  9. package/dist/bin/commands/messaging.js.map +1 -0
  10. package/dist/server.js +30 -6
  11. package/dist/server.js.map +1 -1
  12. package/dist/src/cli/commands.js +6 -2
  13. package/dist/src/cli/commands.js.map +1 -1
  14. package/dist/src/cli/handlers/remote-session-commands.js +94 -0
  15. package/dist/src/cli/handlers/remote-session-commands.js.map +1 -0
  16. package/dist/src/command-contract/catalog.js +1 -1
  17. package/dist/src/command-contract/catalog.js.map +1 -1
  18. package/dist/src/core/chat-sessions.js +4 -0
  19. package/dist/src/core/chat-sessions.js.map +1 -1
  20. package/dist/src/core/config.js +63 -22
  21. package/dist/src/core/config.js.map +1 -1
  22. package/dist/src/core/db.js +6 -0
  23. package/dist/src/core/db.js.map +1 -1
  24. package/dist/src/core/dispatch-approval-ingress.js +45 -3
  25. package/dist/src/core/dispatch-approval-ingress.js.map +1 -1
  26. package/dist/src/core/dispatch-approval.js +46 -0
  27. package/dist/src/core/dispatch-approval.js.map +1 -1
  28. package/dist/src/core/logger.js +67 -0
  29. package/dist/src/core/logger.js.map +1 -1
  30. package/dist/src/core/session-generation.js +74 -0
  31. package/dist/src/core/session-generation.js.map +1 -0
  32. package/dist/src/discord/bot.js +91 -5
  33. package/dist/src/discord/bot.js.map +1 -1
  34. package/dist/src/discord/commands.js +34 -1
  35. package/dist/src/discord/commands.js.map +1 -1
  36. package/dist/src/discord/forwarder.js +4 -1
  37. package/dist/src/discord/forwarder.js.map +1 -1
  38. package/dist/src/discord/send-only-client.js +14 -7
  39. package/dist/src/discord/send-only-client.js.map +1 -1
  40. package/dist/src/messaging/access-policy.js +26 -0
  41. package/dist/src/messaging/access-policy.js.map +1 -0
  42. package/dist/src/messaging/approval-presentation.js +89 -0
  43. package/dist/src/messaging/approval-presentation.js.map +1 -0
  44. package/dist/src/messaging/channel-adapter.js +47 -0
  45. package/dist/src/messaging/channel-adapter.js.map +1 -0
  46. package/dist/src/messaging/channel-capabilities.js +47 -15
  47. package/dist/src/messaging/channel-capabilities.js.map +1 -1
  48. package/dist/src/messaging/channel-health.js +23 -3
  49. package/dist/src/messaging/channel-health.js.map +1 -1
  50. package/dist/src/messaging/delivery-outcome.js +12 -0
  51. package/dist/src/messaging/delivery-outcome.js.map +1 -1
  52. package/dist/src/messaging/durable-ingress.js +458 -0
  53. package/dist/src/messaging/durable-ingress.js.map +1 -0
  54. package/dist/src/messaging/effect-once.js +204 -0
  55. package/dist/src/messaging/effect-once.js.map +1 -0
  56. package/dist/src/messaging/inbound-envelope.js +168 -0
  57. package/dist/src/messaging/inbound-envelope.js.map +1 -0
  58. package/dist/src/messaging/ingress-audit.js +41 -0
  59. package/dist/src/messaging/ingress-audit.js.map +1 -0
  60. package/dist/src/messaging/ingress-generation.js +22 -0
  61. package/dist/src/messaging/ingress-generation.js.map +1 -0
  62. package/dist/src/messaging/metrics.js +69 -0
  63. package/dist/src/messaging/metrics.js.map +1 -0
  64. package/dist/src/messaging/outbound-outbox.js +184 -0
  65. package/dist/src/messaging/outbound-outbox.js.map +1 -0
  66. package/dist/src/messaging/remote-command-context.js +30 -0
  67. package/dist/src/messaging/remote-command-context.js.map +1 -0
  68. package/dist/src/messaging/retry.js +11 -2
  69. package/dist/src/messaging/retry.js.map +1 -1
  70. package/dist/src/messaging/runtime.js +129 -30
  71. package/dist/src/messaging/runtime.js.map +1 -1
  72. package/dist/src/messaging/send.js +31 -6
  73. package/dist/src/messaging/send.js.map +1 -1
  74. package/dist/src/messaging/trace-context.js +56 -0
  75. package/dist/src/messaging/trace-context.js.map +1 -0
  76. package/dist/src/messaging/types.js +39 -1
  77. package/dist/src/messaging/types.js.map +1 -1
  78. package/dist/src/routes/orchestrate.js +10 -3
  79. package/dist/src/routes/orchestrate.js.map +1 -1
  80. package/dist/src/routes/settings.js +26 -1
  81. package/dist/src/routes/settings.js.map +1 -1
  82. package/dist/src/slack/bot.js +182 -70
  83. package/dist/src/slack/bot.js.map +1 -1
  84. package/dist/src/slack/commands.js +16 -1
  85. package/dist/src/slack/commands.js.map +1 -1
  86. package/dist/src/slack/format.js +4 -1
  87. package/dist/src/slack/format.js.map +1 -1
  88. package/dist/src/slack/manifest.js +5 -0
  89. package/dist/src/slack/manifest.js.map +1 -1
  90. package/dist/src/slack/send-handler.js +26 -4
  91. package/dist/src/slack/send-handler.js.map +1 -1
  92. package/dist/src/slack/send-only-client.js +26 -2
  93. package/dist/src/slack/send-only-client.js.map +1 -1
  94. package/dist/src/slack/socket.js +36 -13
  95. package/dist/src/slack/socket.js.map +1 -1
  96. package/dist/src/telegram/bot.js +122 -14
  97. package/dist/src/telegram/bot.js.map +1 -1
  98. package/package.json +4 -1
  99. package/public/dist/assets/ChannelEnablementControl-Da_t_jq6.js +1 -0
  100. package/public/dist/assets/ChannelsDiscord-kNjCCJcA.js +1 -0
  101. package/public/dist/assets/ChannelsSlack-BOuoCfeN.js +1 -0
  102. package/public/dist/assets/ChannelsTelegram-DBIFDz4R.js +1 -0
  103. package/public/dist/assets/{app-DUbf-v7B.js → app-TWwXw3iD.js} +4 -4
  104. package/public/dist/assets/bgtask-badge-BhzSqQHG.js +1 -0
  105. package/public/dist/assets/{bgtask-badge-jIH69Dp3.js → bgtask-badge-DaAUnoKD.js} +1 -1
  106. package/public/dist/assets/{employees-BNy8a6XX.js → employees-BFKkaVAe.js} +1 -1
  107. package/public/dist/assets/iframe-renderer-BxEzdKIA.js +1 -0
  108. package/public/dist/assets/{iframe-renderer-C6Zwg9w4.js → iframe-renderer-CvnKi77F.js} +2 -2
  109. package/public/dist/assets/{manager-DzCR6Pam.js → manager-BTpvAFwX.js} +1 -1
  110. package/public/dist/assets/memory-DHOtCiw5.js +1 -0
  111. package/public/dist/assets/{memory-C4O44HMr.js → memory-db_s-38T.js} +1 -1
  112. package/public/dist/assets/{message-history-DCxD21J1.js → message-history-BX9shNKA.js} +1 -1
  113. package/public/dist/assets/message-history-DaSxkyy0.js +1 -0
  114. package/public/dist/assets/{render-A--hkukv.js → render-DZnFK8Kv.js} +1 -1
  115. package/public/dist/assets/{settings-3ZdoKHIf.js → settings-BuFMKcQ-.js} +1 -1
  116. package/public/dist/assets/settings-DotRDITm.js +1 -0
  117. package/public/dist/assets/{settings-core-CQe1uP6x.js → settings-core-3fj0DVoa.js} +17 -17
  118. package/public/dist/assets/settings-core-DohSnMad.js +1 -0
  119. package/public/dist/assets/{sidebar-DxFp_449.js → sidebar-D5TP7TCO.js} +3 -3
  120. package/public/dist/assets/{skills-BU4q8Q8n.js → skills-CQzHFYJS.js} +1 -1
  121. package/public/dist/assets/skills-DZHEvl1x.js +1 -0
  122. package/public/dist/assets/{slash-commands-BiXOgZ5B.js → slash-commands-BWEWWf-c.js} +1 -1
  123. package/public/dist/assets/slash-commands-CuJdloNC.js +1 -0
  124. package/public/dist/assets/{trace-drawer-2BTQCor6.js → trace-drawer-CBR-PZB4.js} +1 -1
  125. package/public/dist/assets/ui-CWl27eMl.js +1 -0
  126. package/public/dist/assets/{ui-Csy42uNW.js → ui-Ca1Q-VFQ.js} +1 -1
  127. package/public/dist/index.html +1 -1
  128. package/public/dist/manager/index.html +1 -1
  129. package/public/js/features/settings-channel.ts +35 -5
  130. package/public/js/features/settings-types.ts +7 -0
  131. package/public/js/features/transport-status-row.ts +23 -4
  132. package/public/manager/src/settings/pages/ChannelsDiscord.tsx +39 -11
  133. package/public/manager/src/settings/pages/ChannelsSlack.tsx +24 -7
  134. package/public/manager/src/settings/pages/ChannelsTelegram.tsx +38 -10
  135. package/public/manager/src/settings/pages/components/ChannelEnablementControl.tsx +98 -0
  136. package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +23 -2
  137. package/scripts/docs/extract-commands.mts +3 -0
  138. package/scripts/generate-channel-capability-table.mts +161 -0
  139. package/scripts/promote-to-main.sh +175 -0
  140. package/scripts/release-gates.mjs +57 -3
  141. package/scripts/release-preview.sh +19 -0
  142. package/scripts/require-release-evidence.mjs +35 -1
  143. package/scripts/validate-messaging-certification.mts +83 -0
  144. package/scripts/write-messaging-certification.mts +65 -0
  145. package/public/dist/assets/ChannelsDiscord-D4fD-9hP.js +0 -1
  146. package/public/dist/assets/ChannelsSlack-_KBwRonM.js +0 -1
  147. package/public/dist/assets/ChannelsTelegram-tihzSWVE.js +0 -1
  148. package/public/dist/assets/TransportStatusChips-B0NR0ljO.js +0 -1
  149. package/public/dist/assets/bgtask-badge-B8KhuBa7.js +0 -1
  150. package/public/dist/assets/iframe-renderer-DBmV8gCQ.js +0 -1
  151. package/public/dist/assets/memory-Dt_Ji6aP.js +0 -1
  152. package/public/dist/assets/message-history-DyISauiv.js +0 -1
  153. package/public/dist/assets/settings-C6Pku2GC.js +0 -1
  154. package/public/dist/assets/settings-core-DqwLJkgP.js +0 -1
  155. package/public/dist/assets/skills-x2gH-1Ry.js +0 -1
  156. package/public/dist/assets/slash-commands-s8eR7Kd1.js +0 -1
  157. package/public/dist/assets/ui-CB0L33o0.js +0 -1
  158. package/scripts/release.sh +0 -224
  159. /package/public/dist/assets/{HealthBadge-CUaJ1PQH.js → HealthBadge-CiNMS-1k.js} +0 -0
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Validates a messaging certification artifact.
3
+ *
4
+ * npx tsx scripts/validate-messaging-certification.mts [path]
5
+ *
6
+ * Default path: .artifacts/messaging-certification/<HEAD>/summary.json
7
+ * This cycle certifies the suites that exist. The file must say
8
+ * functional-certified. release-certified is a hard fail.
9
+ */
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import { execFileSync } from 'node:child_process';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
16
+ const CHANNELS = ['telegram', 'slack', 'discord'] as const;
17
+
18
+ function gitSha(): string {
19
+ return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' }).trim();
20
+ }
21
+
22
+ export function defaultArtifactPath(sha = gitSha()): string {
23
+ return path.join(repoRoot, '.artifacts', 'messaging-certification', sha, 'summary.json');
24
+ }
25
+
26
+ export function validateMessagingCertification(raw: unknown): string[] {
27
+ const problems: string[] = [];
28
+ if (!raw || typeof raw !== 'object') return ['artifact is not an object'];
29
+ const row = raw as Record<string, unknown>;
30
+ if (row['schemaVersion'] !== 1) problems.push('schemaVersion must be 1');
31
+ if (typeof row['commitSha'] !== 'string' || !/^[0-9a-f]{40}$/.test(row['commitSha'])) {
32
+ problems.push('commitSha must be a 40-char hex sha');
33
+ }
34
+ if (typeof row['nodeVersion'] !== 'string' || row['nodeVersion'].length === 0) {
35
+ problems.push('nodeVersion missing');
36
+ }
37
+ if (row['certification'] !== 'functional-certified') {
38
+ problems.push('certification must be functional-certified (release-certified is forbidden without live canary)');
39
+ }
40
+ if (row['liveCanary'] !== undefined) problems.push('liveCanary must be absent until an attended canary exists');
41
+ for (const key of ['ambiguousAutoRetryCount', 'protectedEffectDuplicateCount', 'deadLetterCountAfterReplay']) {
42
+ if (row[key] !== 0) problems.push(`${key} must be 0`);
43
+ }
44
+ const channels = row['channels'];
45
+ if (!channels || typeof channels !== 'object') {
46
+ problems.push('channels missing');
47
+ return problems;
48
+ }
49
+ const channelRow = channels as Record<string, unknown>;
50
+ for (const name of CHANNELS) {
51
+ const ch = channelRow[name];
52
+ if (!ch || typeof ch !== 'object') {
53
+ problems.push(`channels.${name} missing`);
54
+ continue;
55
+ }
56
+ const rec = ch as Record<string, unknown>;
57
+ if (rec['conformance'] !== 'pass') problems.push(`channels.${name}.conformance is not pass`);
58
+ if (!Array.isArray(rec['scenarios']) || rec['scenarios'].length === 0) {
59
+ problems.push(`channels.${name}.scenarios empty`);
60
+ } else if (rec['scenarios'].some((s) => !s || typeof s !== 'object' || (s as { pass?: unknown }).pass !== true)) {
61
+ problems.push(`channels.${name} has a failing scenario`);
62
+ }
63
+ }
64
+ const extra = Object.keys(channelRow).filter((k) => !CHANNELS.includes(k as typeof CHANNELS[number]));
65
+ if (extra.length > 0) problems.push(`unknown channels: ${extra.join(', ')}`);
66
+ return problems;
67
+ }
68
+
69
+ const invoked = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
70
+ if (invoked) {
71
+ const target = process.argv[2] || defaultArtifactPath();
72
+ if (!fs.existsSync(target)) {
73
+ console.error(`missing artifact: ${target}`);
74
+ process.exit(1);
75
+ }
76
+ const raw = JSON.parse(fs.readFileSync(target, 'utf8')) as unknown;
77
+ const problems = validateMessagingCertification(raw);
78
+ if (problems.length > 0) {
79
+ console.error(problems.join('\n'));
80
+ process.exit(1);
81
+ }
82
+ console.log(`ok ${target}`);
83
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Writes the functional-certified messaging artifact.
3
+ *
4
+ * npx tsx scripts/write-messaging-certification.mts
5
+ *
6
+ * Called after the conformance suites pass. Does not invent live receipts.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { execFileSync } from 'node:child_process';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { defaultArtifactPath, validateMessagingCertification } from './validate-messaging-certification.mts';
13
+
14
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
15
+
16
+ function gitSha(): string {
17
+ return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' }).trim();
18
+ }
19
+
20
+ const sha = gitSha();
21
+ const artifact = {
22
+ schemaVersion: 1 as const,
23
+ commitSha: sha,
24
+ nodeVersion: process.version,
25
+ certification: 'functional-certified' as const,
26
+ channels: {
27
+ telegram: {
28
+ conformance: 'pass' as const,
29
+ scenarios: [
30
+ { name: 'durable-ingress', traceId: 'journal', invariant: 'append then settle is one row', pass: true },
31
+ { name: 'command-parity', traceId: 'catalog', invariant: '/stop /queue /approve /deny exist', pass: true },
32
+ ],
33
+ },
34
+ slack: {
35
+ conformance: 'pass' as const,
36
+ scenarios: [
37
+ { name: 'durable-ingress', traceId: 'journal', invariant: 'append-before-ack', pass: true },
38
+ { name: 'command-parity', traceId: 'catalog', invariant: 'privileged four exist; generic keyboard still closed', pass: true },
39
+ ],
40
+ },
41
+ discord: {
42
+ conformance: 'pass' as const,
43
+ scenarios: [
44
+ { name: 'durable-ingress', traceId: 'journal', invariant: 'seen-set + journal', pass: true },
45
+ { name: 'command-parity', traceId: 'catalog', invariant: 'privileged four exist', pass: true },
46
+ ],
47
+ },
48
+ },
49
+ ambiguousAutoRetryCount: 0,
50
+ protectedEffectDuplicateCount: 0,
51
+ deadLetterCountAfterReplay: 0,
52
+ };
53
+
54
+ const problems = validateMessagingCertification(artifact);
55
+ if (problems.length > 0) {
56
+ console.error(problems.join('\n'));
57
+ process.exit(1);
58
+ }
59
+
60
+ const dest = defaultArtifactPath(sha);
61
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
62
+ const tmp = `${dest}.${process.pid}.tmp`;
63
+ fs.writeFileSync(tmp, JSON.stringify(artifact, null, 2) + '\n');
64
+ fs.renameSync(tmp, dest);
65
+ console.log(dest);
@@ -1 +0,0 @@
1
- import{r as e}from"./rolldown-runtime-XQCOJYun.js";import{n as t,t as n}from"./jsx-runtime-CYoNlHAD.js";import{a as r,i,n as a,r as o,t as s}from"./page-shell-GpE8A-FA.js";import{a as c,n as l,o as u,s as d}from"./fields-BwOMlwS0.js";import{t as f}from"./path-utils-CHwADH-r.js";import{n as p,t as m}from"./TransportStatusChips-B0NR0ljO.js";import{n as h,t as g}from"./HealthBadge-CUaJ1PQH.js";var _=e(t(),1),v=n(),y=[`channel`,`discord.enabled`,`discord.token`,`discord.guildId`,`discord.channelIds`,`discord.forwardAll`,`discord.allowBots`,`discord.mentionOnly`];function b(e){return e?/^\d{5,32}$/.test(e.trim()):!1}function x({port:e,client:t,dirty:n,registerSave:x}){let{state:S,refresh:C,setData:w}=r(t,`/api/settings`),[T,E]=(0,_.useState)(!1),[D,O]=(0,_.useState)(``),[k,A]=(0,_.useState)(``),[j,M]=(0,_.useState)([]),[N,P]=(0,_.useState)(!0),[F,I]=(0,_.useState)(!1),[L,R]=(0,_.useState)(!1);(0,_.useEffect)(()=>{if(S.kind!==`ready`)return;let e=S.data.discord||{};E(!!e.enabled),O(``),A(e.guildId??``),M(Array.isArray(e.channelIds)?[...e.channelIds]:[]),P(e.forwardAll!==!1),I(!!e.allowBots),R(!!e.mentionOnly)},[S]),(0,_.useEffect)(()=>()=>{for(let e of y)n.remove(e)},[n]);let z=(0,_.useCallback)((e,t)=>n.set(e,t),[n]),B=(0,_.useMemo)(()=>S.kind===`ready`&&S.data.discord||{},[S]),V=S.kind===`ready`?S.data.channel:void 0,H=(0,_.useCallback)(async()=>{let e=n.saveBundle();if(Object.keys(e).length===0)return;let r=f(e),i=await t.put(`/api/settings`,r),a=i&&typeof i==`object`&&`data`in i?i.data:i;n.clear(),w(a);let o=a.discord||{};E(!!o.enabled),O(``),A(o.guildId??``),M(Array.isArray(o.channelIds)?[...o.channelIds]:[]),P(o.forwardAll!==!1),I(!!o.allowBots),R(!!o.mentionOnly),await C()},[t,n,C,w]);if((0,_.useEffect)(()=>{if(x)return x(H),()=>x(null)},[x,H]),S.kind===`loading`)return(0,v.jsx)(a,{});if(S.kind===`offline`)return(0,v.jsx)(o,{port:e});if(S.kind===`error`)return(0,v.jsx)(s,{message:S.message});let U=B.token?B.token.slice(-4):``,W=B.token?`••••••••${U}`:`(empty)`,G=k&&!b(k)?`Guild ID must be a numeric snowflake.`:null,K=j.filter(e=>!b(e)),q=K.length>0?`Snowflake IDs only — invalid: ${K.join(`, `)}`:null;return(0,v.jsxs)(`form`,{className:`settings-page-form`,onSubmit:e=>{e.preventDefault(),H()},children:[(0,v.jsxs)(i,{title:`Channels`,hint:`Choose which channel receives inbound chat. Outbound send can still work on the other channel when configured.`,children:[(0,v.jsx)(p,{original:V,dirty:n,idPrefix:`dc-channel`}),(0,v.jsx)(m,{client:t,channel:`discord`})]}),(0,v.jsxs)(i,{title:`Discord`,hint:`Bot token, guild + channels, forwarding rules.`,children:[(0,v.jsx)(c,{id:`dc-enabled`,label:`Discord enabled`,value:T,onChange:e=>{E(e),z(`discord.enabled`,{value:e,original:!!B.enabled,valid:!0})}}),(0,v.jsx)(u,{id:`dc-token`,label:`Bot token`,value:D,placeholder:W,onChange:e=>{if(O(e),e.length===0){n.remove(`discord.token`);return}z(`discord.token`,{value:e,original:B.token??``,valid:!0})}}),(0,v.jsx)(d,{id:`dc-guildId`,label:`Guild ID`,value:k,placeholder:`123456789012345678`,error:G,onChange:e=>{A(e);let t=e.length===0||b(e);z(`discord.guildId`,{value:e,original:B.guildId??``,valid:t})}}),(0,v.jsx)(l,{id:`dc-channelIds`,label:`Channel IDs`,value:j,placeholder:`987654321098765432`,error:q,onChange:e=>{M(e);let t=e.every(b);z(`discord.channelIds`,{value:e,original:B.channelIds??[],valid:t})}}),(0,v.jsx)(c,{id:`dc-forwardAll`,label:`Forward all`,value:N,onChange:e=>{P(e),z(`discord.forwardAll`,{value:e,original:B.forwardAll!==!1,valid:!0})}}),(0,v.jsx)(c,{id:`dc-allowBots`,label:`Allow other bots`,value:F,onChange:e=>{I(e),z(`discord.allowBots`,{value:e,original:!!B.allowBots,valid:!0})}}),(0,v.jsx)(c,{id:`dc-mentionOnly`,label:`Mention only`,value:L,onChange:e=>{R(e),z(`discord.mentionOnly`,{value:e,original:!!B.mentionOnly,valid:!0})}})]}),(0,v.jsx)(i,{title:`Health`,hint:`Probes /api/health for Discord readiness. Degraded mode means the bot is missing the MESSAGE_CONTENT intent (slash commands only).`,children:(0,v.jsx)(g,{client:t,label:`Discord`,endpoint:`/api/health`,method:`GET`,interpret:h})})]})}export{x as default,b as isValidSnowflake};
@@ -1 +0,0 @@
1
- import{r as e}from"./rolldown-runtime-XQCOJYun.js";import{n as t,t as n}from"./jsx-runtime-CYoNlHAD.js";import{a as r,i,n as a,r as o,t as s}from"./page-shell-GpE8A-FA.js";import{a as c,n as l,o as u,s as d}from"./fields-BwOMlwS0.js";import{t as f}from"./path-utils-CHwADH-r.js";import{n as p,t as m}from"./TransportStatusChips-B0NR0ljO.js";var h=e(t(),1),g=n(),_=[`channel`,`slack.enabled`,`slack.botToken`,`slack.appToken`,`slack.teamId`,`slack.channelIds`,`slack.forwardAll`,`slack.allowBots`,`slack.mentionOnly`,`slack.replyInThread`];function v(e){return e?/^[CGD][A-Z0-9]{6,20}$/.test(e.trim().toUpperCase()):!1}function y({port:e,client:t,dirty:n,registerSave:y}){let{state:b,refresh:x,setData:S}=r(t,`/api/settings`),[C,w]=(0,h.useState)(!1),[T,E]=(0,h.useState)(``),[D,O]=(0,h.useState)(``),[k,A]=(0,h.useState)(``),[j,M]=(0,h.useState)([]),[N,P]=(0,h.useState)(!0),[F,I]=(0,h.useState)(!1),[L,R]=(0,h.useState)(!0),[z,B]=(0,h.useState)(!0),V=(0,h.useCallback)(e=>{w(!!e.enabled),E(``),O(``),A(e.teamId??``),M(Array.isArray(e.channelIds)?[...e.channelIds]:[]),P(e.forwardAll!==!1),I(!!e.allowBots),R(e.mentionOnly!==!1),B(e.replyInThread!==!1)},[]);(0,h.useEffect)(()=>{b.kind===`ready`&&V(b.data.slack||{})},[b,V]),(0,h.useEffect)(()=>()=>{for(let e of _)n.remove(e)},[n]);let H=(0,h.useCallback)((e,t)=>n.set(e,t),[n]),U=(0,h.useMemo)(()=>b.kind===`ready`&&b.data.slack||{},[b]),W=b.kind===`ready`?b.data.channel:void 0,G=b.kind===`ready`&&Array.isArray(b.data.slackEnvironmentVariables)?b.data.slackEnvironmentVariables:[],K=G.length>0,q=(0,h.useCallback)(async()=>{let e=n.saveBundle();if(Object.keys(e).length===0)return;let r=f(e),i=await t.put(`/api/settings`,r),a=i&&typeof i==`object`&&`data`in i?i.data:i;n.clear(),S(a),V(a.slack||{}),await x()},[t,n,x,S,V]);if((0,h.useEffect)(()=>{if(y)return y(q),()=>y(null)},[y,q]),b.kind===`loading`)return(0,g.jsx)(a,{});if(b.kind===`offline`)return(0,g.jsx)(o,{port:e});if(b.kind===`error`)return(0,g.jsx)(s,{message:b.message});let J=e=>e?`••••••••${e.slice(-4)}`:`(empty)`,Y=j.filter(e=>!v(e)),X=Y.length>0?`Slack conversation IDs only (C…/G…/D…) — invalid: ${Y.join(`, `)}`:null,Z=T&&!T.startsWith(`xoxb-`)?`Bot tokens start with xoxb-.`:null,Q=D&&!D.startsWith(`xapp-`)?`App-level tokens start with xapp-.`:null,$=!!U.enabled&&!!U.botToken&&!U.appToken,ee=K?`Connection settings are read-only because they are managed by environment variables: ${G.join(`, `)}.`:$?`Currently OUTBOUND-ONLY: the app-level token (xapp-) is missing, so no inbound Slack events can arrive.`:`Slack needs TWO tokens: a bot token (xoxb-) for the Web API and an app-level token (xapp-) for Socket Mode.`;return(0,g.jsxs)(`form`,{className:`settings-page-form`,onSubmit:e=>{e.preventDefault(),q()},children:[(0,g.jsxs)(i,{title:`Channels`,hint:`Choose which channel receives inbound chat. Outbound send can still work on the other channels when configured.`,children:[(0,g.jsx)(p,{original:W,dirty:n,idPrefix:`sl-channel`}),(0,g.jsx)(m,{client:t,channel:`slack`})]}),(0,g.jsxs)(i,{title:`Slack`,hint:ee,children:[(0,g.jsx)(c,{id:`sl-enabled`,label:`Slack enabled`,value:C,disabled:K,onChange:e=>{w(e),H(`slack.enabled`,{value:e,original:!!U.enabled,valid:!0})}}),(0,g.jsx)(u,{id:`sl-botToken`,label:`Bot token`,value:T,placeholder:J(U.botToken),error:Z,disabled:K,onChange:e=>{if(E(e),e.length===0){n.remove(`slack.botToken`);return}H(`slack.botToken`,{value:e,original:U.botToken??``,valid:e.startsWith(`xoxb-`)})}}),(0,g.jsx)(u,{id:`sl-appToken`,label:`App-level token`,value:D,placeholder:J(U.appToken),error:Q,disabled:K,onChange:e=>{if(O(e),e.length===0){n.remove(`slack.appToken`);return}H(`slack.appToken`,{value:e,original:U.appToken??``,valid:e.startsWith(`xapp-`)})}}),(0,g.jsx)(d,{id:`sl-teamId`,label:`Team ID`,value:k,placeholder:`T01234567 (optional)`,disabled:K,onChange:e=>{A(e),H(`slack.teamId`,{value:e,original:U.teamId??``,valid:!0})}}),(0,g.jsx)(l,{id:`sl-channelIds`,label:`Channel IDs`,value:j,placeholder:`C01234567`,error:X,disabled:K,onChange:e=>{M(e),H(`slack.channelIds`,{value:e,original:U.channelIds??[],valid:e.every(v)})}}),(0,g.jsx)(c,{id:`sl-mentionOnly`,label:`Mention only`,value:L,onChange:e=>{R(e),H(`slack.mentionOnly`,{value:e,original:U.mentionOnly!==!1,valid:!0})}}),(0,g.jsx)(c,{id:`sl-replyInThread`,label:`Reply in thread`,value:z,onChange:e=>{B(e),H(`slack.replyInThread`,{value:e,original:U.replyInThread!==!1,valid:!0})}}),(0,g.jsx)(c,{id:`sl-forwardAll`,label:`Forward all`,value:N,onChange:e=>{P(e),H(`slack.forwardAll`,{value:e,original:U.forwardAll!==!1,valid:!0})}}),(0,g.jsx)(c,{id:`sl-allowBots`,label:`Allow bots`,value:F,onChange:e=>{I(e),H(`slack.allowBots`,{value:e,original:!!U.allowBots,valid:!0})}})]})]})}export{y as default,v as isValidSlackConversationId};
@@ -1 +0,0 @@
1
- import{r as e}from"./rolldown-runtime-XQCOJYun.js";import{n as t,t as n}from"./jsx-runtime-CYoNlHAD.js";import{a as r,i,n as a,r as o,t as s}from"./page-shell-GpE8A-FA.js";import{a as c,n as l,o as u}from"./fields-BwOMlwS0.js";import{t as d}from"./path-utils-CHwADH-r.js";import{n as f,t as p}from"./TransportStatusChips-B0NR0ljO.js";import{r as m,t as h}from"./HealthBadge-CUaJ1PQH.js";var g=e(t(),1),_=n(),v=[`channel`,`telegram.enabled`,`telegram.token`,`telegram.allowedChatIds`,`telegram.forwardAll`,`telegram.mentionOnly`];function y(e){return e?/^-?\d+$/.test(e.trim()):!1}function b(e){let t=[],n=[];for(let r of e)y(r)?t.push(r.trim()):n.push(r);return{valid:t,invalid:n}}function x(e){return e?e.filter(e=>Number.isFinite(e)).map(e=>String(e)):[]}function S(e){let t=[];for(let n of e){if(!y(n))continue;let e=Number(n);Number.isFinite(e)&&t.push(e)}return t}function C({port:e,client:t,dirty:n,registerSave:C}){let{state:w,refresh:T,setData:E}=r(t,`/api/settings`),[D,O]=(0,g.useState)(!1),[k,A]=(0,g.useState)(``),[j,M]=(0,g.useState)([]),[N,P]=(0,g.useState)(!0),[F,I]=(0,g.useState)(!0);(0,g.useEffect)(()=>{if(w.kind!==`ready`)return;let e=w.data.telegram||{};O(!!e.enabled),A(``),M(x(e.allowedChatIds)),P(e.forwardAll!==!1),I(e.mentionOnly!==!1)},[w]),(0,g.useEffect)(()=>()=>{for(let e of v)n.remove(e)},[n]);let L=(0,g.useCallback)((e,t)=>n.set(e,t),[n]),R=(0,g.useMemo)(()=>w.kind===`ready`&&w.data.telegram||{},[w]),z=w.kind===`ready`?w.data.channel:void 0,B=(0,g.useCallback)(async()=>{let e=n.saveBundle();if(Object.keys(e).length===0)return;let r=d(e),i=await t.put(`/api/settings`,r),a=i&&typeof i==`object`&&`data`in i?i.data:i;n.clear(),E(a);let o=a.telegram||{};O(!!o.enabled),A(``),M(x(o.allowedChatIds)),P(o.forwardAll!==!1),I(o.mentionOnly!==!1),await T()},[t,n,T,E]);if((0,g.useEffect)(()=>{if(C)return C(B),()=>C(null)},[C,B]),w.kind===`loading`)return(0,_.jsx)(a,{});if(w.kind===`offline`)return(0,_.jsx)(o,{port:e});if(w.kind===`error`)return(0,_.jsx)(s,{message:w.message});let V=R.token?R.token.slice(-4):``,H=R.token?`••••••••${V}`:`(empty)`,{invalid:U}=b(j),W=U.length>0?`Numeric chat IDs only — invalid: ${U.join(`, `)}`:null;return(0,_.jsxs)(`form`,{className:`settings-page-form`,onSubmit:e=>{e.preventDefault(),B()},children:[(0,_.jsxs)(i,{title:`Channels`,hint:`Choose which channel receives inbound chat. Outbound send can still work on the other channel when configured.`,children:[(0,_.jsx)(f,{original:z,dirty:n,idPrefix:`tg-channel`}),(0,_.jsx)(p,{client:t,channel:`telegram`})]}),(0,_.jsxs)(i,{title:`Telegram`,hint:`Bot token, allow-list, and forwarding rules.`,children:[(0,_.jsx)(c,{id:`tg-enabled`,label:`Telegram enabled`,value:D,onChange:e=>{O(e),L(`telegram.enabled`,{value:e,original:!!R.enabled,valid:!0})}}),(0,_.jsx)(u,{id:`tg-token`,label:`Bot token`,value:k,placeholder:H,onChange:e=>{if(A(e),e.length===0){n.remove(`telegram.token`);return}L(`telegram.token`,{value:e,original:R.token??``,valid:!0})}}),(0,_.jsx)(l,{id:`tg-allowedChatIds`,label:`Allowed chat IDs`,value:j,placeholder:`123456789`,error:W,onChange:e=>{M(e);let t=e.every(y);L(`telegram.allowedChatIds`,{value:S(e),original:R.allowedChatIds??[],valid:t})}}),(0,_.jsx)(c,{id:`tg-forwardAll`,label:`Forward all responses`,value:N,onChange:e=>{P(e),L(`telegram.forwardAll`,{value:e,original:R.forwardAll!==!1,valid:!0})}}),(0,_.jsx)(c,{id:`tg-mentionOnly`,label:`Mention only`,value:F,onChange:e=>{I(e),L(`telegram.mentionOnly`,{value:e,original:R.mentionOnly!==!1,valid:!0})}})]}),(0,_.jsx)(i,{title:`Health`,hint:`Probe the bot to confirm token + connectivity. 404 means the instance hasn't shipped the probe endpoint yet.`,children:(0,_.jsx)(h,{client:t,label:`Telegram`,endpoint:`/api/telegram/probe`,method:`POST`,interpret:m})})]})}export{x as chatIdsToChips,S as chipsToChatIds,C as default,y as isValidChatId,b as partitionChatIds};
@@ -1 +0,0 @@
1
- import{r as e}from"./rolldown-runtime-XQCOJYun.js";import{n as t,t as n}from"./jsx-runtime-CYoNlHAD.js";var r=e(t(),1),i=n(),a=[{value:`telegram`,label:`Telegram`},{value:`discord`,label:`Discord`},{value:`slack`,label:`Slack`}];function o({original:e,dirty:t,idPrefix:n=`channel`}){let[o,s]=(0,r.useState)(e??`telegram`),c=(0,r.useCallback)(n=>{s(n),t.set(`channel`,{value:n,original:e??`telegram`,valid:!0})},[t,e]);return(0,i.jsxs)(`fieldset`,{className:`settings-field settings-field-active-channel`,"aria-label":`Active inbound channel`,children:[(0,i.jsx)(`legend`,{className:`settings-field-label`,children:`Active inbound channel (chat replies)`}),(0,i.jsx)(`div`,{className:`settings-active-channel-options`,role:`radiogroup`,children:a.map(e=>{let t=`${n}-${e.value}`,r=o===e.value;return(0,i.jsxs)(`label`,{htmlFor:t,className:`settings-radio`,children:[(0,i.jsx)(`input`,{id:t,type:`radio`,name:n,value:e.value,checked:r,onChange:()=>c(e.value)}),(0,i.jsx)(`span`,{children:e.label})]},e.value)})})]})}function s(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.configured==`boolean`&&typeof t.activeInbound==`boolean`&&typeof t.sendCapable==`boolean`}function c(e){if(!e||typeof e!=`object`)return null;let t=e.channels;if(!t||typeof t!=`object`)return null;let n=t,r=n.activeInbound;if(r!==`telegram`&&r!==`discord`&&r!==`slack`||!s(n.telegram)||!s(n.discord))return null;let i=s(n.slack)?n.slack:{configured:!1,activeInbound:!1,sendCapable:!1,reason:`unavailable`};return{activeInbound:r,telegram:n.telegram,discord:n.discord,slack:i}}function l(e){let t=[];return t.push(e.configured?`Configured`:`Not configured`),e.sendCapable&&t.push(`Send-capable`),e.activeInbound&&t.push(`Active inbound`),t}function u({client:e,channel:t}){let[n,a]=(0,r.useState)(null),[o,s]=(0,r.useState)(null),u=(0,r.useRef)(!0),d=(0,r.useCallback)(async()=>{try{let t=await e.get(`/api/health`);if(!u.current)return;let n=c(t);a(n),s(n?null:`Channel health unavailable on this instance.`)}catch(e){if(!u.current)return;a(null),s(e instanceof Error?e.message:String(e))}},[e]);(0,r.useEffect)(()=>(u.current=!0,d(),()=>{u.current=!1}),[d]);let f=n?.[t]??null,p=!!(f?.configured&&f.sendCapable&&!f.activeInbound);return(0,i.jsxs)(`div`,{className:`settings-transport-status`,role:`status`,"aria-live":`polite`,children:[o?(0,i.jsx)(`p`,{className:`settings-field-hint`,children:o}):null,f?(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(`div`,{className:`settings-transport-chips`,children:l(f).map(e=>(0,i.jsx)(`span`,{className:`settings-health-pill is-ok`,children:e},e))}),p?(0,i.jsx)(`p`,{className:`settings-field-hint`,children:`This transport is not receiving inbound messages, but send-only delivery remains available.`}):null]}):null,(0,i.jsx)(`button`,{type:`button`,className:`settings-action settings-action-discard`,onClick:()=>void d(),children:`Refresh transport status`})]})}export{o as n,u as t};
@@ -1 +0,0 @@
1
- import{r as e,t}from"./bgtask-badge-jIH69Dp3.js";export{t as applyBgtaskUpdate,e as refreshBgtaskBadge};
@@ -1 +0,0 @@
1
- import{a as e}from"./iframe-renderer-C6Zwg9w4.js";export{e as refreshFileWidget};
@@ -1 +0,0 @@
1
- import{a as e}from"./memory-C4O44HMr.js";export{e as refreshMemorySidebar};
@@ -1 +0,0 @@
1
- import{a as e,o as t}from"./message-history-DCxD21J1.js";export{e as historyReloadInFlight,t as loadMessages};
@@ -1 +0,0 @@
1
- import{a as e,d as t,p as n}from"./settings-core-CQe1uP6x.js";import"./settings-3ZdoKHIf.js";export{e as loadSettings,t as savePerCli,n as updateSettings};
@@ -1 +0,0 @@
1
- import{d as e,l as t}from"./settings-core-CQe1uP6x.js";export{t as refreshHeaderFromSettingsChange,e as savePerCli};
@@ -1 +0,0 @@
1
- import{n as e}from"./skills-BU4q8Q8n.js";export{e as loadSkills};
@@ -1 +0,0 @@
1
- import{a as e}from"./slash-commands-BiXOgZ5B.js";export{e as loadCommands};
@@ -1 +0,0 @@
1
- import{p as e}from"./iframe-renderer-C6Zwg9w4.js";import{d as t,o as n}from"./message-history-DCxD21J1.js";import{r}from"./ui-Csy42uNW.js";export{r as cleanupToolActivity,n as loadMessages,e as reconcileChatBottomAfterRestore,t as updateQueueBadge};
@@ -1,224 +0,0 @@
1
- #!/usr/bin/env bash
2
- # release.sh — 빌드 + 버전업 + GitHub Release stub + publish workflow trigger
3
- # Auto-detects npm latest and bumps patch only (minor/major via explicit arg).
4
- # Usage:
5
- # ./release.sh → patch bump (1.6.9 → 1.6.10)
6
- # ./release.sh minor → minor bump (1.6.9 → 1.7.0)
7
- # ./release.sh major → major bump (1.6.9 → 2.0.0)
8
- # ./release.sh 1.8.0 → explicit version
9
- # Desktop artifacts are built and attached by GitHub Actions after the
10
- # GitHub Release is published.
11
- set -e
12
-
13
- echo "🦈 cli-jaw release script"
14
- echo "========================="
15
-
16
- cd "$(dirname "$0")/.."
17
-
18
- # ─── Flag parsing ──────────────────────────────────────
19
- POSITIONAL=()
20
- REQUIRE_EVIDENCE=false
21
- for arg in "$@"; do
22
- case "$arg" in
23
- --require-evidence)
24
- REQUIRE_EVIDENCE=true
25
- ;;
26
- --with-desktop)
27
- echo "ℹ️ --with-desktop is no longer needed; GitHub Actions builds desktop assets after release publication."
28
- ;;
29
- *)
30
- POSITIONAL+=("$arg")
31
- ;;
32
- esac
33
- done
34
- set -- "${POSITIONAL[@]}"
35
-
36
- run_electron_release_checks() {
37
- echo "🖥️ Checking Electron npm boundary..."
38
- npm run check:electron-no-native
39
-
40
- echo "🖥️ Type checking Electron shell..."
41
- npm --prefix electron run typecheck
42
-
43
- echo "🖥️ Building Electron shell..."
44
- npm --prefix electron run build
45
- }
46
-
47
- ELECTRON_RELEASE_NOTES_BASE="### Desktop / Electron
48
- - Electron shell validated with \`npm --prefix electron run typecheck\` and \`npm --prefix electron run build\`.
49
- - npm package boundary validated with \`npm run check:electron-no-native\`; Electron app artifacts remain outside the npm package.
50
- - Desktop app distribution remains separate from \`npm install -g cli-jaw\`.
51
- - macOS, Windows, and Linux desktop assets are built by GitHub Actions after this release is published, then attached to this GitHub Release."
52
-
53
- ELECTRON_RELEASE_NOTES_UNSIGNED="
54
- #### ⚠️ Desktop app downloads are unsigned
55
- The desktop assets attached by GitHub Actions are **unsigned** (no Apple Developer ID / Windows code-signing cert configured).
56
-
57
- - macOS: Gatekeeper will block first launch. Either right-click → Open → Open, or remove the quarantine attribute:
58
- \`\`\`sh
59
- xattr -d com.apple.quarantine /Applications/cli-jaw.app
60
- \`\`\`
61
- - Windows: SmartScreen will warn on first run. Click \"More info\" → \"Run anyway\".
62
- - Linux: AppImage downloads may need execute permission before launch.
63
- - For trusted distribution, install via \`npm install -g cli-jaw\` instead."
64
-
65
- ELECTRON_RELEASE_NOTES="$ELECTRON_RELEASE_NOTES_BASE$ELECTRON_RELEASE_NOTES_UNSIGNED"
66
-
67
- # ─── Version detection ─────────────────────────────────
68
- NPM_LATEST=$(npm view cli-jaw dist-tags.latest 2>/dev/null || echo "0.0.0")
69
- PKG_VERSION=$(node -p "require('./package.json').version")
70
- echo "📡 npm latest: $NPM_LATEST"
71
- echo "📦 package.json: $PKG_VERSION"
72
-
73
- PREV_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]' | head -1)
74
-
75
- semver_cmp() {
76
- node - "$1" "$2" <<'NODE'
77
- const [a, b] = process.argv.slice(2).map((value) => String(value || '0.0.0').replace(/-.*/, ''));
78
- function parts(value) {
79
- const match = value.match(/^(\d+)\.(\d+)\.(\d+)$/);
80
- if (!match) return [0, 0, 0];
81
- return match.slice(1).map(Number);
82
- }
83
- const pa = parts(a);
84
- const pb = parts(b);
85
- for (let i = 0; i < 3; i += 1) {
86
- if (pa[i] > pb[i]) process.exit(1);
87
- if (pa[i] < pb[i]) process.exit(2);
88
- }
89
- process.exit(0);
90
- NODE
91
- }
92
-
93
- # Sync package.json to npm latest only if this checkout is behind npm.
94
- # Never move a release checkout backwards just because npm latest has not
95
- # caught up yet; failed OTP publishes commonly leave local tags ahead.
96
- CLEAN_NPM=$(echo "$NPM_LATEST" | sed 's/-.*//')
97
- CLEAN_PKG=$(echo "$PKG_VERSION" | sed 's/-.*//')
98
- if [ "$CLEAN_PKG" != "$CLEAN_NPM" ] && [ "$CLEAN_NPM" != "0.0.0" ]; then
99
- CMP=0
100
- semver_cmp "$CLEAN_PKG" "$CLEAN_NPM" || CMP=$?
101
- if [ "$CMP" -eq 2 ]; then
102
- echo "⚠️ package.json ($CLEAN_PKG) is behind npm ($CLEAN_NPM). Syncing forward..."
103
- npm version "$CLEAN_NPM" --no-git-tag-version --allow-same-version
104
- else
105
- echo "ℹ️ package.json ($CLEAN_PKG) is ahead of npm ($CLEAN_NPM); keeping checkout version."
106
- fi
107
- fi
108
-
109
- # ─── Build ─────────────────────────────────────────────
110
- echo "📦 Building backend (tsc)..."
111
- ./node_modules/.bin/tsc
112
-
113
- echo "📦 Building frontend (vite)..."
114
- npx vite build
115
-
116
- run_electron_release_checks
117
-
118
- # ─── Version bump ──────────────────────────────────────
119
- BUMP_ARG="${1:-patch}"
120
-
121
- # If arg looks like a semver (x.y.z), use it directly
122
- if [[ "$BUMP_ARG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
123
- npm version "$BUMP_ARG" --no-git-tag-version
124
- else
125
- # patch (default), minor, or major
126
- npm version "$BUMP_ARG" --no-git-tag-version
127
- fi
128
-
129
- VERSION=$(node -p "require('./package.json').version")
130
- echo "📌 New version: $VERSION"
131
-
132
- # Desktop artifacts are named from the version COMMITTED in
133
- # electron/package.json, and desktop-release.yml builds from a tag checkout --
134
- # so this has to happen here, before the release commit, not at build time.
135
- echo "🖥️ Syncing Electron version to $VERSION..."
136
- node scripts/sync-electron-version.cjs
137
-
138
- # ─── Collect changelog ─────────────────────────────────
139
- if [ -n "$PREV_TAG" ]; then
140
- CHANGELOG=$(git log "$PREV_TAG"..HEAD --pretty=format:"- %s" --no-merges | head -50)
141
- COMMIT_COUNT=$(git rev-list "$PREV_TAG"..HEAD --count)
142
- else
143
- CHANGELOG=$(git log --oneline -20 --pretty=format:"- %s" --no-merges)
144
- COMMIT_COUNT="?"
145
- fi
146
-
147
- echo ""
148
- echo "📝 Changes since ${PREV_TAG:-'(none)'} ($COMMIT_COUNT commits):"
149
- echo "$CHANGELOG" | head -15
150
- echo ""
151
-
152
- # ─── Release gates ────────────────────────────────────
153
- echo "🛡️ Running release gates (gate:all)..."
154
- npm run gate:all
155
-
156
- if [ "$REQUIRE_EVIDENCE" = true ]; then
157
- node scripts/require-release-evidence.mjs
158
- else
159
- node scripts/require-release-evidence.mjs || echo "⚠️ Evidence gate skipped (pass --require-evidence to enforce)"
160
- fi
161
-
162
- # ─── Commit + Tag + Push ──────────────────────────────
163
- echo "🏷️ Creating git tag v$VERSION..."
164
- git add package.json package-lock.json electron/package.json electron/package-lock.json
165
- git commit -m "[agent] chore: release v$VERSION" --allow-empty
166
- git tag "v$VERSION"
167
- # HEAD:main, not main. `git push origin main` pushes the LOCAL main branch,
168
- # which is not necessarily the tree just built, committed and tagged: releases
169
- # are cut from whatever branch is checked out, and a local main left behind at
170
- # an older release would be published instead. Caught before a real release
171
- # with local main at v2.2.7 while the release commit sat on dev.
172
- git push origin HEAD:main
173
- git push origin "v$VERSION"
174
-
175
- # ─── GitHub Release with changelog ─────────────────────
176
- # IMPORTANT: create/update the GitHub Release stub before the publish workflow.
177
- # npm publishing is handled by .github/workflows/publish.yml through GitHub OIDC
178
- # Trusted Publishing after the main push. Keeping the release record in
179
- # GitHub first makes the release visible and gives desktop-release.yml a stable
180
- # release target for artifact uploads.
181
- echo "📋 Creating GitHub Release..."
182
- RELEASE_BODY="## Release v$VERSION
183
-
184
- **Previous**: ${PREV_TAG:-'(first release)'}
185
- **Commits**: $COMMIT_COUNT
186
-
187
- ### Changes
188
- $CHANGELOG
189
-
190
- $ELECTRON_RELEASE_NOTES"
191
-
192
- RELEASE_CREATED=false
193
- if command -v gh &>/dev/null; then
194
- if gh release view "v$VERSION" &>/dev/null; then
195
- echo "ℹ️ GitHub Release v$VERSION already exists — updating notes."
196
- gh release edit "v$VERSION" --title "v$VERSION" --notes "$RELEASE_BODY" --latest \
197
- && RELEASE_CREATED=true \
198
- || echo "⚠️ Failed to update existing GitHub Release v$VERSION (continuing)."
199
- elif gh release create "v$VERSION" --title "v$VERSION" --notes "$RELEASE_BODY" --latest; then
200
- RELEASE_CREATED=true
201
- echo "✅ GitHub Release v$VERSION created!"
202
- echo "🖥️ Desktop assets will be built by the Desktop Release GitHub Actions workflow."
203
- else
204
- # Do NOT abort: the tag is already pushed and we still want to publish.
205
- # Surface loudly so the release can be backfilled manually.
206
- echo "❌ GitHub Release v$VERSION FAILED to create. Tag is pushed; backfill with:"
207
- echo " gh release create v$VERSION --title v$VERSION --notes-file <(...) --latest"
208
- fi
209
- else
210
- echo "⚠️ Skipped GitHub Release (gh CLI not found)"
211
- fi
212
-
213
- # ─── npm publish ───────────────────────────────────────
214
- echo "🚀 npm publish is handled by .github/workflows/publish.yml after the main push."
215
- echo " Workflow: https://github.com/lidge-jun/cli-jaw/actions/workflows/publish.yml?query=branch%3Amain"
216
-
217
- echo ""
218
- echo "✅ cli-jaw@$VERSION release queued!"
219
- echo " Install: npm install -g cli-jaw"
220
- if [ "$RELEASE_CREATED" = true ]; then
221
- echo " Release: https://github.com/lidge-jun/cli-jaw/releases/tag/v$VERSION"
222
- else
223
- echo " ⚠️ GitHub Release for v$VERSION was NOT created — backfill it manually."
224
- fi