infernoflow 0.37.1 → 0.37.3

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 (88) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/bin/infernoflow.mjs +29 -277
  3. package/dist/lib/adopters/angular.mjs +1 -128
  4. package/dist/lib/adopters/css.mjs +1 -111
  5. package/dist/lib/adopters/react.mjs +1 -104
  6. package/dist/lib/ai/ideDetection.mjs +1 -31
  7. package/dist/lib/ai/localProvider.mjs +1 -88
  8. package/dist/lib/ai/providerRouter.mjs +2 -295
  9. package/dist/lib/commands/adopt.mjs +20 -869
  10. package/dist/lib/commands/adoptWizard.mjs +9 -320
  11. package/dist/lib/commands/agent.mjs +5 -191
  12. package/dist/lib/commands/ai.mjs +2 -407
  13. package/dist/lib/commands/ask.mjs +4 -299
  14. package/dist/lib/commands/audit.mjs +13 -300
  15. package/dist/lib/commands/changelog.mjs +26 -594
  16. package/dist/lib/commands/check.mjs +3 -184
  17. package/dist/lib/commands/ci.mjs +3 -208
  18. package/dist/lib/commands/claudeMd.mjs +30 -135
  19. package/dist/lib/commands/cloud.mjs +10 -773
  20. package/dist/lib/commands/context.mjs +34 -346
  21. package/dist/lib/commands/coverage.mjs +2 -282
  22. package/dist/lib/commands/dashboard.mjs +123 -635
  23. package/dist/lib/commands/demo.mjs +8 -465
  24. package/dist/lib/commands/diff.mjs +5 -274
  25. package/dist/lib/commands/docGate.mjs +2 -81
  26. package/dist/lib/commands/doctor.mjs +3 -321
  27. package/dist/lib/commands/explain.mjs +8 -438
  28. package/dist/lib/commands/export.mjs +10 -239
  29. package/dist/lib/commands/feedback.mjs +12 -216
  30. package/dist/lib/commands/generateSkills.mjs +38 -163
  31. package/dist/lib/commands/graph.mjs +11 -378
  32. package/dist/lib/commands/health.mjs +2 -309
  33. package/dist/lib/commands/impact.mjs +2 -325
  34. package/dist/lib/commands/implement.mjs +7 -103
  35. package/dist/lib/commands/init.mjs +45 -631
  36. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  37. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  38. package/dist/lib/commands/link.mjs +2 -342
  39. package/dist/lib/commands/log.mjs +18 -248
  40. package/dist/lib/commands/monorepo.mjs +4 -428
  41. package/dist/lib/commands/notify.mjs +4 -258
  42. package/dist/lib/commands/onboard.mjs +4 -296
  43. package/dist/lib/commands/prComment.mjs +2 -361
  44. package/dist/lib/commands/prImpact.mjs +2 -157
  45. package/dist/lib/commands/publish.mjs +15 -316
  46. package/dist/lib/commands/recap.mjs +6 -380
  47. package/dist/lib/commands/report.mjs +28 -272
  48. package/dist/lib/commands/review.mjs +9 -223
  49. package/dist/lib/commands/run.mjs +8 -336
  50. package/dist/lib/commands/scaffold.mjs +54 -419
  51. package/dist/lib/commands/scan.mjs +11 -1118
  52. package/dist/lib/commands/scout.mjs +2 -291
  53. package/dist/lib/commands/setup.mjs +5 -310
  54. package/dist/lib/commands/share.mjs +13 -196
  55. package/dist/lib/commands/snapshot.mjs +3 -383
  56. package/dist/lib/commands/stability.mjs +2 -293
  57. package/dist/lib/commands/stats.mjs +5 -402
  58. package/dist/lib/commands/status.mjs +4 -172
  59. package/dist/lib/commands/suggest.mjs +21 -563
  60. package/dist/lib/commands/switch.mjs +13 -520
  61. package/dist/lib/commands/syncAuto.mjs +1 -96
  62. package/dist/lib/commands/synthesize.mjs +10 -228
  63. package/dist/lib/commands/teamSync.mjs +2 -388
  64. package/dist/lib/commands/test.mjs +6 -363
  65. package/dist/lib/commands/theme.mjs +18 -195
  66. package/dist/lib/commands/uninstall.mjs +13 -406
  67. package/dist/lib/commands/upgrade.mjs +20 -153
  68. package/dist/lib/commands/version.mjs +2 -282
  69. package/dist/lib/commands/vibe.mjs +7 -357
  70. package/dist/lib/commands/watch.mjs +4 -203
  71. package/dist/lib/commands/why.mjs +4 -358
  72. package/dist/lib/cursorHooksInstall.mjs +1 -60
  73. package/dist/lib/draftToolingInstall.mjs +7 -68
  74. package/dist/lib/git/detect-drift.mjs +4 -208
  75. package/dist/lib/learning/adapt.mjs +6 -101
  76. package/dist/lib/learning/observe.mjs +1 -119
  77. package/dist/lib/learning/patternDetector.mjs +1 -298
  78. package/dist/lib/learning/profile.mjs +2 -279
  79. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  80. package/dist/lib/telemetry.mjs +19 -269
  81. package/dist/lib/templates/index.mjs +1 -131
  82. package/dist/lib/theme/scanner.mjs +4 -343
  83. package/dist/lib/ui/errors.mjs +1 -142
  84. package/dist/lib/ui/output.mjs +6 -95
  85. package/dist/lib/ui/prompts.mjs +6 -147
  86. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  87. package/package.json +2 -4
  88. package/scripts/postinstall.js +2 -2
@@ -1,228 +1,10 @@
1
- /**
2
- * infernoflow synthesize
3
- *
4
- * Analyzes your observed development sessions and auto-proposes reusable
5
- * skills and agents based on repeated patterns.
6
- *
7
- * Usage:
8
- * infernoflow synthesize interactive review of candidates
9
- * infernoflow synthesize --auto — auto-approve high-confidence (≥0.8)
10
- * infernoflow synthesize --json — print candidates as JSON, exit
11
- * infernoflow synthesize --threshold 2 — surface patterns seen 2+ times
12
- * infernoflow synthesize --watch — re-run every 60s, surface new candidates
13
- */
14
-
15
- import * as fs from "node:fs";
16
- import * as path from "node:path";
17
- import * as readline from "node:readline";
18
- import { header, ok, warn, info, done, bold, cyan, yellow, green, gray } from "../ui/output.mjs";
19
- import { readProfile, writeProfile } from "../learning/profile.mjs";
20
- import { detectPatterns, mergeCandidates, pendingCandidates } from "../learning/patternDetector.mjs";
21
- import { synthesizeCandidate } from "../learning/skillSynthesizer.mjs";
22
- import { observeCommandStart } from "../learning/observe.mjs";
23
-
24
- function findInfernoDir(cwd) {
25
- const infernoDir = path.join(cwd, "inferno");
26
- if (!fs.existsSync(infernoDir)) return null;
27
- return infernoDir;
28
- }
29
-
30
- function ask(rl, question) {
31
- return new Promise(resolve => rl.question(question, a => resolve(a.trim().toLowerCase())));
32
- }
33
-
34
- function renderCandidate(c, index) {
35
- const type = c.type === "agent" ? cyan("agent") : green("skill");
36
- const conf = Math.round(c.confidence * 100);
37
- const confStr = conf >= 80 ? green(`${conf}%`) : conf >= 60 ? yellow(`${conf}%`) : gray(`${conf}%`);
38
- const freq = gray(`seen ${c.frequency}×`);
39
-
40
- console.log(`\n ${bold(`[${index + 1}]`)} ${type} ${bold(c.name)} ${confStr} confidence ${freq}`);
41
- console.log(` Pattern: ${cyan(c.trigger)}`);
42
- if (c.steps) console.log(` Steps: ${c.steps.join(" → ")}`);
43
- if (c.examples?.length) {
44
- console.log(` Examples: ${c.examples.slice(0, 2).map(e => `"${e}"`).join(", ")}`);
45
- }
46
- }
47
-
48
- async function reviewInteractive(cwd, infernoDir, profile, candidates) {
49
- if (candidates.length === 0) {
50
- info("No new candidates to review.");
51
- return { approved: 0, rejected: 0 };
52
- }
53
-
54
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
55
- let approved = 0, rejected = 0;
56
-
57
- console.log(`\n ${bold("Candidates found:")} ${candidates.length}\n`);
58
- console.log(gray(" For each: [y] approve [n] reject [s] skip [q] quit\n"));
59
-
60
- for (let i = 0; i < candidates.length; i++) {
61
- const c = candidates[i];
62
- renderCandidate(c, i);
63
-
64
- const ans = await ask(rl, `\n Approve? [y/n/s/q]: `);
65
-
66
- if (ans === "q") break;
67
- if (ans === "s") continue;
68
-
69
- const all = [...(profile.agentCandidates || []), ...(profile.skillCandidates || [])];
70
- const entry = all.find(x => x.id === c.id);
71
- if (!entry) continue;
72
-
73
- if (ans === "y") {
74
- entry.status = "approved";
75
- try {
76
- const { filePaths } = synthesizeCandidate(cwd, infernoDir, c);
77
- const approvedList = c.type === "agent" ? "approvedAgents" : "approvedSkills";
78
- if (!profile[approvedList]) profile[approvedList] = [];
79
- profile[approvedList].push({
80
- id: c.id,
81
- name: c.name,
82
- filePaths,
83
- approvedAt: new Date().toISOString(),
84
- });
85
- ok(` ${c.type === "agent" ? "Agent" : "Skill"} created:`);
86
- for (const fp of filePaths) console.log(` ${green("→")} ${fp}`);
87
- approved++;
88
- } catch (err) {
89
- warn(` Could not write files: ${err.message}`);
90
- }
91
- } else {
92
- entry.status = "rejected";
93
- rejected++;
94
- }
95
- }
96
-
97
- rl.close();
98
- return { approved, rejected };
99
- }
100
-
101
- function autoApprove(cwd, infernoDir, profile, candidates, threshold = 0.8) {
102
- let approved = 0;
103
- for (const c of candidates) {
104
- if (c.confidence < threshold) continue;
105
-
106
- const all = [...(profile.agentCandidates || []), ...(profile.skillCandidates || [])];
107
- const entry = all.find(x => x.id === c.id);
108
- if (!entry) continue;
109
-
110
- entry.status = "approved";
111
- try {
112
- const { filePaths } = synthesizeCandidate(cwd, infernoDir, c);
113
- const approvedList = c.type === "agent" ? "approvedAgents" : "approvedSkills";
114
- if (!profile[approvedList]) profile[approvedList] = [];
115
- profile[approvedList].push({
116
- id: c.id,
117
- name: c.name,
118
- filePaths,
119
- approvedAt: new Date().toISOString(),
120
- });
121
- ok(`Auto-approved ${c.type}: ${bold(c.name)} (${Math.round(c.confidence * 100)}% confidence)`);
122
- for (const fp of filePaths) console.log(` ${green("→")} ${fp}`);
123
- approved++;
124
- } catch (err) {
125
- warn(`Could not write ${c.name}: ${err.message}`);
126
- }
127
- }
128
- return approved;
129
- }
130
-
131
- export async function synthesizeCommand(args) {
132
- const cwd = process.cwd();
133
- const isAuto = args.includes("--auto");
134
- const isJson = args.includes("--json");
135
- const isWatch = args.includes("--watch");
136
- const threshold = (() => {
137
- const idx = args.indexOf("--threshold");
138
- return idx !== -1 ? Number(args[idx + 1]) || 3 : 3;
139
- })();
140
-
141
- const infernoDir = findInfernoDir(cwd);
142
- if (!infernoDir) {
143
- if (isJson) {
144
- process.stdout.write(JSON.stringify({ ok: false, error: "inferno/ not found" }) + "\n");
145
- } else {
146
- warn("inferno/ not found — run infernoflow init first");
147
- }
148
- process.exit(1);
149
- }
150
-
151
- observeCommandStart(infernoDir, "synthesize");
152
-
153
- async function runOnce() {
154
- const profile = readProfile(infernoDir);
155
-
156
- // ── Detect patterns ──────────────────────────────────────────────────────
157
- const { agentCandidates, skillCandidates } = detectPatterns(profile, { minFreq: threshold });
158
- mergeCandidates(profile, { agentCandidates, skillCandidates });
159
-
160
- const pending = pendingCandidates(profile);
161
-
162
- // ── JSON mode ────────────────────────────────────────────────────────────
163
- if (isJson) {
164
- writeProfile(infernoDir, profile);
165
- process.stdout.write(JSON.stringify({
166
- ok: true,
167
- pendingCount: pending.length,
168
- candidates: pending,
169
- sessions: profile.recentSessions?.length || 0,
170
- }, null, 2) + "\n");
171
- return;
172
- }
173
-
174
- header("infernoflow synthesize");
175
- info(`Sessions analyzed: ${bold(String(profile.recentSessions?.length || 0))}`);
176
- info(`Minimum frequency: ${bold(String(threshold))}×`);
177
-
178
- if (pending.length === 0) {
179
- console.log();
180
- if ((profile.recentSessions?.length || 0) < threshold) {
181
- warn(`Not enough session data yet — need at least ${threshold} sessions with similar commands.`);
182
- warn(`Keep using infernoflow and run synthesize again soon.`);
183
- } else {
184
- ok("No new patterns detected — your workflow is already well captured.");
185
- }
186
- writeProfile(infernoDir, profile);
187
- return;
188
- }
189
-
190
- info(`New patterns detected: ${bold(String(pending.length))}`);
191
-
192
- // ── Auto mode ────────────────────────────────────────────────────────────
193
- let approved;
194
- if (isAuto) {
195
- info(`Auto-approving candidates with ≥80% confidence...`);
196
- approved = autoApprove(cwd, infernoDir, profile, pending);
197
- } else {
198
- const result = await reviewInteractive(cwd, infernoDir, profile, pending);
199
- approved = result.approved;
200
- if (result.rejected > 0) info(`Rejected: ${result.rejected}`);
201
- }
202
-
203
- writeProfile(infernoDir, profile);
204
-
205
- if (approved > 0) {
206
- console.log();
207
- done(`${approved} skill${approved !== 1 ? "s/agents" : "/agent"} synthesized`);
208
- console.log(`\n ${bold("What was created:")}`);
209
- for (const item of [...(profile.approvedSkills || []), ...(profile.approvedAgents || [])].slice(-approved)) {
210
- const type = profile.approvedAgents?.find(a => a.id === item.id) ? "agent" : "skill";
211
- console.log(` ${green("✔")} ${type}: ${bold(item.name)}`);
212
- for (const fp of item.filePaths || []) console.log(` ${gray(fp)}`);
213
- }
214
- console.log(`\n ${bold("Next:")} Run ${cyan("infernoflow agent list")} to see your agents`);
215
- console.log(` or ${cyan("infernoflow agent run <name>")} to execute one`);
216
- }
217
- }
218
-
219
- if (isWatch) {
220
- info("Watching for new patterns — press Ctrl+C to stop");
221
- const INTERVAL = 60_000;
222
- await runOnce();
223
- setInterval(runOnce, INTERVAL);
224
- await new Promise(() => {}); // keep alive
225
- } else {
226
- await runOnce();
227
- }
228
- }
1
+ import*as j from"node:fs";import*as x from"node:path";import*as q from"node:readline";import{header as N,ok as C,warn as $,info as g,done as P,bold as f,cyan as w,yellow as z,green as h,gray as y}from"../ui/output.mjs";import{readProfile as I,writeProfile as k}from"../learning/profile.mjs";import{detectPatterns as O,mergeCandidates as D,pendingCandidates as b}from"../learning/patternDetector.mjs";import{synthesizeCandidate as A}from"../learning/skillSynthesizer.mjs";import{observeCommandStart as L}from"../learning/observe.mjs";function J(t){const i=x.join(t,"inferno");return j.existsSync(i)?i:null}function M(t,i){return new Promise(o=>t.question(i,s=>o(s.trim().toLowerCase())))}function R(t,i){const o=t.type==="agent"?w("agent"):h("skill"),s=Math.round(t.confidence*100),u=s>=80?h(`${s}%`):s>=60?z(`${s}%`):y(`${s}%`),a=y(`seen ${t.frequency}\xD7`);console.log(`
2
+ ${f(`[${i+1}]`)} ${o} ${f(t.name)} ${u} confidence ${a}`),console.log(` Pattern: ${w(t.trigger)}`),t.steps&&console.log(` Steps: ${t.steps.join(" \u2192 ")}`),t.examples?.length&&console.log(` Examples: ${t.examples.slice(0,2).map(n=>`"${n}"`).join(", ")}`)}async function W(t,i,o,s){if(s.length===0)return g("No new candidates to review."),{approved:0,rejected:0};const u=q.createInterface({input:process.stdin,output:process.stdout});let a=0,n=0;console.log(`
3
+ ${f("Candidates found:")} ${s.length}
4
+ `),console.log(y(` For each: [y] approve [n] reject [s] skip [q] quit
5
+ `));for(let p=0;p<s.length;p++){const e=s[p];R(e,p);const c=await M(u,`
6
+ Approve? [y/n/s/q]: `);if(c==="q")break;if(c==="s")continue;const l=[...o.agentCandidates||[],...o.skillCandidates||[]].find(r=>r.id===e.id);if(l)if(c==="y"){l.status="approved";try{const{filePaths:r}=A(t,i,e),d=e.type==="agent"?"approvedAgents":"approvedSkills";o[d]||(o[d]=[]),o[d].push({id:e.id,name:e.name,filePaths:r,approvedAt:new Date().toISOString()}),C(` ${e.type==="agent"?"Agent":"Skill"} created:`);for(const v of r)console.log(` ${h("\u2192")} ${v}`);a++}catch(r){$(` Could not write files: ${r.message}`)}}else l.status="rejected",n++}return u.close(),{approved:a,rejected:n}}function E(t,i,o,s,u=.8){let a=0;for(const n of s){if(n.confidence<u)continue;const e=[...o.agentCandidates||[],...o.skillCandidates||[]].find(c=>c.id===n.id);if(e){e.status="approved";try{const{filePaths:c}=A(t,i,n),m=n.type==="agent"?"approvedAgents":"approvedSkills";o[m]||(o[m]=[]),o[m].push({id:n.id,name:n.name,filePaths:c,approvedAt:new Date().toISOString()}),C(`Auto-approved ${n.type}: ${f(n.name)} (${Math.round(n.confidence*100)}% confidence)`);for(const l of c)console.log(` ${h("\u2192")} ${l}`);a++}catch(c){$(`Could not write ${n.name}: ${c.message}`)}}}return a}async function B(t){const i=process.cwd(),o=t.includes("--auto"),s=t.includes("--json"),u=t.includes("--watch"),a=(()=>{const e=t.indexOf("--threshold");return e!==-1&&Number(t[e+1])||3})(),n=J(i);n||(s?process.stdout.write(JSON.stringify({ok:!1,error:"inferno/ not found"})+`
7
+ `):$("inferno/ not found \u2014 run infernoflow init first"),process.exit(1)),L(n,"synthesize");async function p(){const e=I(n),{agentCandidates:c,skillCandidates:m}=O(e,{minFreq:a});D(e,{agentCandidates:c,skillCandidates:m});const l=b(e);if(s){k(n,e),process.stdout.write(JSON.stringify({ok:!0,pendingCount:l.length,candidates:l,sessions:e.recentSessions?.length||0},null,2)+`
8
+ `);return}if(N("infernoflow synthesize"),g(`Sessions analyzed: ${f(String(e.recentSessions?.length||0))}`),g(`Minimum frequency: ${f(String(a))}\xD7`),l.length===0){console.log(),(e.recentSessions?.length||0)<a?($(`Not enough session data yet \u2014 need at least ${a} sessions with similar commands.`),$("Keep using infernoflow and run synthesize again soon.")):C("No new patterns detected \u2014 your workflow is already well captured."),k(n,e);return}g(`New patterns detected: ${f(String(l.length))}`);let r;if(o)g("Auto-approving candidates with \u226580% confidence..."),r=E(i,n,e,l);else{const d=await W(i,n,e,l);r=d.approved,d.rejected>0&&g(`Rejected: ${d.rejected}`)}if(k(n,e),r>0){console.log(),P(`${r} skill${r!==1?"s/agents":"/agent"} synthesized`),console.log(`
9
+ ${f("What was created:")}`);for(const d of[...e.approvedSkills||[],...e.approvedAgents||[]].slice(-r)){const v=e.approvedAgents?.find(S=>S.id===d.id)?"agent":"skill";console.log(` ${h("\u2714")} ${v}: ${f(d.name)}`);for(const S of d.filePaths||[])console.log(` ${y(S)}`)}console.log(`
10
+ ${f("Next:")} Run ${w("infernoflow agent list")} to see your agents`),console.log(` or ${w("infernoflow agent run <name>")} to execute one`)}}if(u){g("Watching for new patterns \u2014 press Ctrl+C to stop");const e=6e4;await p(),setInterval(p,e),await new Promise(()=>{})}else await p()}export{B as synthesizeCommand};
@@ -1,388 +1,2 @@
1
- /**
2
- * infernoflow team-sync
3
- *
4
- * Shared capability contract sync across a team.
5
- * Uses a dedicated git branch (`inferno-contracts`) as the source of truth.
6
- *
7
- * Sub-commands:
8
- * infernoflow team-sync push — push local contract to shared branch
9
- * infernoflow team-sync pull — pull shared contract, detect conflicts
10
- * infernoflow team-sync status — show diff between local and shared
11
- * infernoflow team-sync init — create the shared branch if it doesn't exist
12
- *
13
- * Flags:
14
- * --branch <name> Shared branch name (default: inferno-contracts)
15
- * --remote <name> Git remote (default: origin)
16
- * --json Machine-readable output
17
- * --force Overwrite conflicts without prompting
18
- */
19
-
20
- import * as fs from "node:fs";
21
- import * as path from "node:path";
22
- import { execSync } from "node:child_process";
23
- import { header, ok, warn, info, done, bold, cyan, gray, green, red, yellow } from "../ui/output.mjs";
24
-
25
- // ── git helpers ───────────────────────────────────────────────────────────────
26
-
27
- function capture(cmd, cwd) {
28
- try {
29
- return execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
30
- } catch { return null; }
31
- }
32
-
33
- function run(cmd, cwd) {
34
- execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
35
- }
36
-
37
- function currentBranch(cwd) {
38
- return capture("git rev-parse --abbrev-ref HEAD", cwd) || "HEAD";
39
- }
40
-
41
- function currentUser(cwd) {
42
- return capture("git config user.name", cwd) || capture("git config user.email", cwd) || "unknown";
43
- }
44
-
45
- function hasRemote(remote, cwd) {
46
- return !!capture(`git remote get-url ${remote}`, cwd);
47
- }
48
-
49
- function branchExistsRemote(remote, branch, cwd) {
50
- return !!capture(`git ls-remote --heads ${remote} refs/heads/${branch}`, cwd);
51
- }
52
-
53
- // ── capability helpers ────────────────────────────────────────────────────────
54
-
55
- function parseCaps(jsonText) {
56
- if (!jsonText) return [];
57
- try {
58
- const obj = JSON.parse(jsonText);
59
- const raw = obj.capabilities || [];
60
- return raw.map(c => typeof c === "string" ? { id: c, title: c } : c);
61
- } catch { return []; }
62
- }
63
-
64
- function capsToMap(caps) {
65
- return new Map(caps.map(c => [c.id, c]));
66
- }
67
-
68
- function detectConflicts(local, shared, base) {
69
- // A conflict occurs when BOTH local and shared changed the same capability
70
- // since the last sync (base).
71
- const localMap = capsToMap(local);
72
- const sharedMap = capsToMap(shared);
73
- const baseMap = capsToMap(base);
74
-
75
- const conflicts = [];
76
- const localOnly = [];
77
- const sharedOnly = [];
78
-
79
- const allIds = new Set([...localMap.keys(), ...sharedMap.keys(), ...baseMap.keys()]);
80
-
81
- for (const id of allIds) {
82
- const localCap = localMap.get(id);
83
- const sharedCap = sharedMap.get(id);
84
- const baseCap = baseMap.get(id);
85
-
86
- const localChanged = JSON.stringify(localCap) !== JSON.stringify(baseCap);
87
- const sharedChanged = JSON.stringify(sharedCap) !== JSON.stringify(baseCap);
88
-
89
- if (localChanged && sharedChanged && JSON.stringify(localCap) !== JSON.stringify(sharedCap)) {
90
- conflicts.push({ id, local: localCap, shared: sharedCap, base: baseCap });
91
- } else if (localCap && !sharedCap && !baseCap) {
92
- localOnly.push(localCap); // added locally, not in shared yet
93
- } else if (!localCap && sharedCap && !baseCap) {
94
- sharedOnly.push(sharedCap); // added in shared, not locally yet
95
- }
96
- }
97
-
98
- return { conflicts, localOnly, sharedOnly };
99
- }
100
-
101
- // ── shared branch operations ──────────────────────────────────────────────────
102
-
103
- function readContractFromBranch(remote, branch, cwd) {
104
- // Fetch the branch first
105
- try { run(`git fetch ${remote} ${branch} --quiet`, cwd); } catch {}
106
-
107
- const content = capture(`git show ${remote}/${branch}:inferno/contract.json`, cwd);
108
- if (!content) return null;
109
- try { return JSON.parse(content); } catch { return null; }
110
- }
111
-
112
- function readLastSyncBase(infernoDir) {
113
- const basePath = path.join(infernoDir, ".team-sync-base.json");
114
- if (!fs.existsSync(basePath)) return null;
115
- try { return JSON.parse(fs.readFileSync(basePath, "utf8")); } catch { return null; }
116
- }
117
-
118
- function writeLastSyncBase(infernoDir, contract) {
119
- const basePath = path.join(infernoDir, ".team-sync-base.json");
120
- fs.writeFileSync(basePath, JSON.stringify(contract, null, 2), "utf8");
121
- }
122
-
123
- // ── sub-commands ──────────────────────────────────────────────────────────────
124
-
125
- function initSharedBranch(cwd, remote, branch, infernoDir, asJson) {
126
- if (!hasRemote(remote, cwd)) {
127
- const msg = `Remote "${remote}" not found. Add it first: git remote add ${remote} <url>`;
128
- if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
129
- warn(msg); process.exit(1);
130
- }
131
-
132
- if (branchExistsRemote(remote, branch, cwd)) {
133
- if (asJson) { console.log(JSON.stringify({ ok: true, action: "already_exists", branch })); }
134
- else { ok(`Shared branch ${bold(branch)} already exists on ${remote}`); }
135
- return;
136
- }
137
-
138
- // Create orphan branch with just the contract
139
- const contractPath = path.join(infernoDir, "contract.json");
140
- if (!fs.existsSync(contractPath)) {
141
- const msg = "inferno/contract.json not found — run: infernoflow init";
142
- if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
143
- warn(msg); process.exit(1);
144
- }
145
-
146
- // Use a temp worktree approach: push contract.json to the branch
147
- const tmpDir = path.join(infernoDir, ".team-sync-tmp");
148
- try {
149
- fs.mkdirSync(tmpDir, { recursive: true });
150
- const contractContent = fs.readFileSync(contractPath, "utf8");
151
- fs.writeFileSync(path.join(tmpDir, "contract.json"), contractContent);
152
-
153
- // Create an empty tree commit on the shared branch
154
- run(`git checkout --orphan ${branch}`, cwd);
155
- run(`git rm -rf . --quiet 2>/dev/null || true`, cwd);
156
- run(`git checkout ${currentBranch(cwd)} -- inferno/contract.json`, cwd);
157
- run(`git add inferno/contract.json`, cwd);
158
- run(`git commit -m "infernoflow: initialize shared contract branch"`, cwd);
159
- run(`git push ${remote} ${branch}`, cwd);
160
- run(`git checkout -`, cwd); // back to previous branch
161
- } catch (err) {
162
- // Restore original branch on failure
163
- try { run(`git checkout -`, cwd); } catch {}
164
- const msg = `Failed to create shared branch: ${err.message}`;
165
- if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
166
- warn(msg); process.exit(1);
167
- } finally {
168
- try { fs.rmSync(tmpDir, { recursive: true }); } catch {}
169
- }
170
-
171
- if (asJson) { console.log(JSON.stringify({ ok: true, action: "created", branch, remote })); }
172
- else { done(`Shared branch ${bold(branch)} created on ${bold(remote)}`); }
173
- }
174
-
175
- function pushToShared(cwd, remote, branch, infernoDir, asJson, force) {
176
- const contractPath = path.join(infernoDir, "contract.json");
177
- if (!fs.existsSync(contractPath)) {
178
- const msg = "inferno/contract.json not found";
179
- if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
180
- warn(msg); process.exit(1);
181
- }
182
-
183
- const localContract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
184
- const user = currentUser(cwd);
185
-
186
- // Stamp the push metadata
187
- localContract._teamSync = {
188
- pushedBy: user,
189
- pushedAt: new Date().toISOString(),
190
- fromBranch: currentBranch(cwd),
191
- };
192
-
193
- // Write updated contract
194
- fs.writeFileSync(contractPath, JSON.stringify(localContract, null, 2), "utf8");
195
-
196
- // Commit + push to shared branch
197
- try {
198
- run(`git fetch ${remote} ${branch} --quiet`, cwd);
199
- // Use a temporary stash-push approach: push just the contract file
200
- capture(`git stash --quiet`, cwd);
201
- try {
202
- run(`git checkout ${remote}/${branch} -- inferno/contract.json 2>/dev/null || git checkout ${remote}/${branch} inferno/contract.json`, cwd);
203
- } catch {}
204
- capture(`git stash pop --quiet`, cwd);
205
-
206
- // Write the updated content
207
- fs.writeFileSync(contractPath, JSON.stringify(localContract, null, 2), "utf8");
208
- run(`git add inferno/contract.json`, cwd);
209
- run(`git commit -m "infernoflow team-sync: push by ${user}"`, cwd);
210
- run(`git push ${remote} HEAD:${branch}`, cwd);
211
-
212
- // Save base snapshot
213
- writeLastSyncBase(infernoDir, localContract);
214
-
215
- if (asJson) { console.log(JSON.stringify({ ok: true, action: "pushed", remote, branch, user })); }
216
- else { done(`Contract pushed to ${bold(remote + "/" + branch)} by ${bold(user)}`); }
217
- } catch (err) {
218
- const msg = `Push failed: ${err.message}`;
219
- if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
220
- warn(msg);
221
- info(`Try: git push ${remote} HEAD:${branch} --force (use --force flag)`);
222
- process.exit(1);
223
- }
224
- }
225
-
226
- function pullFromShared(cwd, remote, branch, infernoDir, asJson, force) {
227
- const contractPath = path.join(infernoDir, "contract.json");
228
-
229
- // Fetch remote contract
230
- const sharedContract = readContractFromBranch(remote, branch, cwd);
231
- if (!sharedContract) {
232
- const msg = `Could not read contract from ${remote}/${branch}. Run: infernoflow team-sync init`;
233
- if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
234
- warn(msg); process.exit(1);
235
- }
236
-
237
- const localContract = fs.existsSync(contractPath)
238
- ? JSON.parse(fs.readFileSync(contractPath, "utf8")) : { capabilities: [] };
239
-
240
- const baseContract = readLastSyncBase(infernoDir) || { capabilities: [] };
241
-
242
- const localCaps = parseCaps(JSON.stringify(localContract));
243
- const sharedCaps = parseCaps(JSON.stringify(sharedContract));
244
- const baseCaps = parseCaps(JSON.stringify(baseContract));
245
-
246
- const { conflicts, localOnly, sharedOnly } = detectConflicts(localCaps, sharedCaps, baseCaps);
247
-
248
- if (conflicts.length > 0 && !force) {
249
- if (asJson) {
250
- console.log(JSON.stringify({ ok: false, error: "conflicts_detected", conflicts, hint: "Use --force to overwrite with remote version" }));
251
- process.exit(1);
252
- }
253
- warn(`${conflicts.length} capability conflict${conflicts.length !== 1 ? "s" : ""} detected:\n`);
254
- for (const c of conflicts) {
255
- console.log(` ${red("✗")} ${bold(c.id)}`);
256
- console.log(` local: ${gray(c.local?.title || "(removed)")}`);
257
- console.log(` shared: ${gray(c.shared?.title || "(removed)")}`);
258
- }
259
- console.log();
260
- warn("Resolve conflicts manually or use --force to take the shared version");
261
- process.exit(1);
262
- }
263
-
264
- // Merge: take shared as base, apply localOnly additions
265
- const merged = { ...sharedContract };
266
- const mergedCaps = [...sharedCaps];
267
- for (const cap of localOnly) mergedCaps.push(cap);
268
- merged.capabilities = mergedCaps;
269
- delete merged._teamSync;
270
-
271
- fs.writeFileSync(contractPath, JSON.stringify(merged, null, 2), "utf8");
272
- writeLastSyncBase(infernoDir, merged);
273
-
274
- if (asJson) {
275
- console.log(JSON.stringify({
276
- ok: true, action: "pulled", remote, branch,
277
- conflicts: conflicts.length,
278
- localOnly: localOnly.length,
279
- sharedOnly: sharedOnly.length,
280
- }));
281
- } else {
282
- console.log();
283
- ok("Contract updated from shared branch");
284
- if (conflicts.length > 0) warn(`${conflicts.length} conflict(s) resolved with --force (shared version wins)`);
285
- if (localOnly.length > 0) ok(`${localOnly.length} local capability(-ies) preserved`);
286
- if (sharedOnly.length > 0) ok(`${sharedOnly.length} new capability(-ies) pulled from shared`);
287
- if (conflicts.length === 0 && localOnly.length === 0 && sharedOnly.length === 0) {
288
- info("Already in sync — no changes");
289
- }
290
- console.log();
291
- }
292
- }
293
-
294
- function showStatus(cwd, remote, branch, infernoDir, asJson) {
295
- const contractPath = path.join(infernoDir, "contract.json");
296
-
297
- try { run(`git fetch ${remote} ${branch} --quiet`, cwd); } catch {}
298
-
299
- const sharedContract = readContractFromBranch(remote, branch, cwd);
300
- if (!sharedContract) {
301
- const msg = `Shared branch ${remote}/${branch} not found. Run: infernoflow team-sync init`;
302
- if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
303
- warn(msg); process.exit(1);
304
- }
305
-
306
- const localContract = fs.existsSync(contractPath)
307
- ? JSON.parse(fs.readFileSync(contractPath, "utf8")) : { capabilities: [] };
308
-
309
- const localCaps = parseCaps(JSON.stringify(localContract));
310
- const sharedCaps = parseCaps(JSON.stringify(sharedContract));
311
-
312
- const localMap = capsToMap(localCaps);
313
- const sharedMap = capsToMap(sharedCaps);
314
-
315
- const onlyLocal = localCaps.filter(c => !sharedMap.has(c.id));
316
- const onlyShared = sharedCaps.filter(c => !localMap.has(c.id));
317
- const inSync = onlyLocal.length === 0 && onlyShared.length === 0;
318
- const pushedBy = sharedContract._teamSync?.pushedBy || "unknown";
319
- const pushedAt = sharedContract._teamSync?.pushedAt || "unknown";
320
-
321
- if (asJson) {
322
- console.log(JSON.stringify({
323
- ok: true, inSync,
324
- local: localCaps.length, shared: sharedCaps.length,
325
- onlyLocal: onlyLocal.map(c => c.id),
326
- onlyShared: onlyShared.map(c => c.id),
327
- lastPush: { by: pushedBy, at: pushedAt },
328
- }));
329
- return;
330
- }
331
-
332
- console.log();
333
- console.log(` Shared branch ${bold(cyan(remote + "/" + branch))}`);
334
- console.log(` Last push ${bold(pushedBy)} ${gray(pushedAt.slice(0, 19).replace("T", " "))}`);
335
- console.log();
336
-
337
- if (inSync) {
338
- ok("Local and shared contracts are in sync");
339
- } else {
340
- if (onlyLocal.length) {
341
- console.log(` ${yellow("→")} ${bold(onlyLocal.length)} local capability(-ies) not yet pushed:`);
342
- for (const c of onlyLocal) console.log(` ${yellow("+")} ${c.id} ${gray(c.title)}`);
343
- }
344
- if (onlyShared.length) {
345
- console.log(` ${cyan("←")} ${bold(onlyShared.length)} shared capability(-ies) not yet pulled:`);
346
- for (const c of onlyShared) console.log(` ${cyan("+")} ${c.id} ${gray(c.title)}`);
347
- }
348
- console.log();
349
- if (onlyLocal.length) info(`Run ${cyan("infernoflow team-sync push")} to share your changes`);
350
- if (onlyShared.length) info(`Run ${cyan("infernoflow team-sync pull")} to get team changes`);
351
- }
352
-
353
- console.log();
354
- }
355
-
356
- // ── main ──────────────────────────────────────────────────────────────────────
357
-
358
- export async function teamSyncCommand(rawArgs) {
359
- const args = rawArgs.slice(1);
360
- const asJson = args.includes("--json");
361
- const force = args.includes("--force");
362
-
363
- const branchIdx = args.indexOf("--branch");
364
- const remoteIdx = args.indexOf("--remote");
365
- const branch = branchIdx !== -1 ? args[branchIdx + 1] : "inferno-contracts";
366
- const remote = remoteIdx !== -1 ? args[remoteIdx + 1] : "origin";
367
-
368
- const sub = args.find(a => !a.startsWith("-")) || "status";
369
-
370
- const cwd = process.cwd();
371
- const infernoDir = path.join(cwd, "inferno");
372
-
373
- if (!asJson) header("infernoflow team-sync");
374
-
375
- if (!fs.existsSync(infernoDir)) {
376
- const msg = "inferno/ not found — run: infernoflow init";
377
- if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
378
- warn(msg); process.exit(1);
379
- }
380
-
381
- switch (sub) {
382
- case "init": initSharedBranch(cwd, remote, branch, infernoDir, asJson); break;
383
- case "push": pushToShared(cwd, remote, branch, infernoDir, asJson, force); break;
384
- case "pull": pullFromShared(cwd, remote, branch, infernoDir, asJson, force); break;
385
- case "status":
386
- default: showStatus(cwd, remote, branch, infernoDir, asJson); break;
387
- }
388
- }
1
+ import*as g from"node:fs";import*as N from"node:path";import{execSync as A}from"node:child_process";import{header as L,ok as j,warn as S,info as F,done as B,bold as m,cyan as b,gray as C,red as _,yellow as P}from"../ui/output.mjs";function x(t,n){try{return A(t,{cwd:n,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()}catch{return null}}function p(t,n){A(t,{cwd:n,encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function R(t){return x("git rev-parse --abbrev-ref HEAD",t)||"HEAD"}function E(t){return x("git config user.name",t)||x("git config user.email",t)||"unknown"}function H(t,n){return!!x(`git remote get-url ${t}`,n)}function I(t,n,e){return!!x(`git ls-remote --heads ${t} refs/heads/${n}`,e)}function v(t){if(!t)return[];try{return(JSON.parse(t).capabilities||[]).map(s=>typeof s=="string"?{id:s,title:s}:s)}catch{return[]}}function w(t){return new Map(t.map(n=>[n.id,n]))}function D(t,n,e){const s=w(t),c=w(n),d=w(e),i=[],o=[],l=[],f=new Set([...s.keys(),...c.keys(),...d.keys()]);for(const u of f){const h=s.get(u),y=c.get(u),r=d.get(u),$=JSON.stringify(h)!==JSON.stringify(r),O=JSON.stringify(y)!==JSON.stringify(r);$&&O&&JSON.stringify(h)!==JSON.stringify(y)?i.push({id:u,local:h,shared:y,base:r}):h&&!y&&!r?o.push(h):!h&&y&&!r&&l.push(y)}return{conflicts:i,localOnly:o,sharedOnly:l}}function M(t,n,e){try{p(`git fetch ${t} ${n} --quiet`,e)}catch{}const s=x(`git show ${t}/${n}:inferno/contract.json`,e);if(!s)return null;try{return JSON.parse(s)}catch{return null}}function T(t){const n=N.join(t,".team-sync-base.json");if(!g.existsSync(n))return null;try{return JSON.parse(g.readFileSync(n,"utf8"))}catch{return null}}function q(t,n){const e=N.join(t,".team-sync-base.json");g.writeFileSync(e,JSON.stringify(n,null,2),"utf8")}function U(t,n,e,s,c){if(!H(n,t)){const o=`Remote "${n}" not found. Add it first: git remote add ${n} <url>`;c&&(console.log(JSON.stringify({ok:!1,error:o})),process.exit(1)),S(o),process.exit(1)}if(I(n,e,t)){c?console.log(JSON.stringify({ok:!0,action:"already_exists",branch:e})):j(`Shared branch ${m(e)} already exists on ${n}`);return}const d=N.join(s,"contract.json");if(!g.existsSync(d)){const o="inferno/contract.json not found \u2014 run: infernoflow init";c&&(console.log(JSON.stringify({ok:!1,error:o})),process.exit(1)),S(o),process.exit(1)}const i=N.join(s,".team-sync-tmp");try{g.mkdirSync(i,{recursive:!0});const o=g.readFileSync(d,"utf8");g.writeFileSync(N.join(i,"contract.json"),o),p(`git checkout --orphan ${e}`,t),p("git rm -rf . --quiet 2>/dev/null || true",t),p(`git checkout ${R(t)} -- inferno/contract.json`,t),p("git add inferno/contract.json",t),p('git commit -m "infernoflow: initialize shared contract branch"',t),p(`git push ${n} ${e}`,t),p("git checkout -",t)}catch(o){try{p("git checkout -",t)}catch{}const l=`Failed to create shared branch: ${o.message}`;c&&(console.log(JSON.stringify({ok:!1,error:l})),process.exit(1)),S(l),process.exit(1)}finally{try{g.rmSync(i,{recursive:!0})}catch{}}c?console.log(JSON.stringify({ok:!0,action:"created",branch:e,remote:n})):B(`Shared branch ${m(e)} created on ${m(n)}`)}function z(t,n,e,s,c,d){const i=N.join(s,"contract.json");if(!g.existsSync(i)){const f="inferno/contract.json not found";c&&(console.log(JSON.stringify({ok:!1,error:f})),process.exit(1)),S(f),process.exit(1)}const o=JSON.parse(g.readFileSync(i,"utf8")),l=E(t);o._teamSync={pushedBy:l,pushedAt:new Date().toISOString(),fromBranch:R(t)},g.writeFileSync(i,JSON.stringify(o,null,2),"utf8");try{p(`git fetch ${n} ${e} --quiet`,t),x("git stash --quiet",t);try{p(`git checkout ${n}/${e} -- inferno/contract.json 2>/dev/null || git checkout ${n}/${e} inferno/contract.json`,t)}catch{}x("git stash pop --quiet",t),g.writeFileSync(i,JSON.stringify(o,null,2),"utf8"),p("git add inferno/contract.json",t),p(`git commit -m "infernoflow team-sync: push by ${l}"`,t),p(`git push ${n} HEAD:${e}`,t),q(s,o),c?console.log(JSON.stringify({ok:!0,action:"pushed",remote:n,branch:e,user:l})):B(`Contract pushed to ${m(n+"/"+e)} by ${m(l)}`)}catch(f){const u=`Push failed: ${f.message}`;c&&(console.log(JSON.stringify({ok:!1,error:u})),process.exit(1)),S(u),F(`Try: git push ${n} HEAD:${e} --force (use --force flag)`),process.exit(1)}}function W(t,n,e,s,c,d){const i=N.join(s,"contract.json"),o=M(n,e,t);if(!o){const k=`Could not read contract from ${n}/${e}. Run: infernoflow team-sync init`;c&&(console.log(JSON.stringify({ok:!1,error:k})),process.exit(1)),S(k),process.exit(1)}const l=g.existsSync(i)?JSON.parse(g.readFileSync(i,"utf8")):{capabilities:[]},f=T(s)||{capabilities:[]},u=v(JSON.stringify(l)),h=v(JSON.stringify(o)),y=v(JSON.stringify(f)),{conflicts:r,localOnly:$,sharedOnly:O}=D(u,h,y);if(r.length>0&&!d){c&&(console.log(JSON.stringify({ok:!1,error:"conflicts_detected",conflicts:r,hint:"Use --force to overwrite with remote version"})),process.exit(1)),S(`${r.length} capability conflict${r.length!==1?"s":""} detected:
2
+ `);for(const k of r)console.log(` ${_("\u2717")} ${m(k.id)}`),console.log(` local: ${C(k.local?.title||"(removed)")}`),console.log(` shared: ${C(k.shared?.title||"(removed)")}`);console.log(),S("Resolve conflicts manually or use --force to take the shared version"),process.exit(1)}const J={...o},a=[...h];for(const k of $)a.push(k);J.capabilities=a,delete J._teamSync,g.writeFileSync(i,JSON.stringify(J,null,2),"utf8"),q(s,J),c?console.log(JSON.stringify({ok:!0,action:"pulled",remote:n,branch:e,conflicts:r.length,localOnly:$.length,sharedOnly:O.length})):(console.log(),j("Contract updated from shared branch"),r.length>0&&S(`${r.length} conflict(s) resolved with --force (shared version wins)`),$.length>0&&j(`${$.length} local capability(-ies) preserved`),O.length>0&&j(`${O.length} new capability(-ies) pulled from shared`),r.length===0&&$.length===0&&O.length===0&&F("Already in sync \u2014 no changes"),console.log())}function G(t,n,e,s,c){const d=N.join(s,"contract.json");try{p(`git fetch ${n} ${e} --quiet`,t)}catch{}const i=M(n,e,t);if(!i){const a=`Shared branch ${n}/${e} not found. Run: infernoflow team-sync init`;c&&(console.log(JSON.stringify({ok:!1,error:a})),process.exit(1)),S(a),process.exit(1)}const o=g.existsSync(d)?JSON.parse(g.readFileSync(d,"utf8")):{capabilities:[]},l=v(JSON.stringify(o)),f=v(JSON.stringify(i)),u=w(l),h=w(f),y=l.filter(a=>!h.has(a.id)),r=f.filter(a=>!u.has(a.id)),$=y.length===0&&r.length===0,O=i._teamSync?.pushedBy||"unknown",J=i._teamSync?.pushedAt||"unknown";if(c){console.log(JSON.stringify({ok:!0,inSync:$,local:l.length,shared:f.length,onlyLocal:y.map(a=>a.id),onlyShared:r.map(a=>a.id),lastPush:{by:O,at:J}}));return}if(console.log(),console.log(` Shared branch ${m(b(n+"/"+e))}`),console.log(` Last push ${m(O)} ${C(J.slice(0,19).replace("T"," "))}`),console.log(),$)j("Local and shared contracts are in sync");else{if(y.length){console.log(` ${P("\u2192")} ${m(y.length)} local capability(-ies) not yet pushed:`);for(const a of y)console.log(` ${P("+")} ${a.id} ${C(a.title)}`)}if(r.length){console.log(` ${b("\u2190")} ${m(r.length)} shared capability(-ies) not yet pulled:`);for(const a of r)console.log(` ${b("+")} ${a.id} ${C(a.title)}`)}console.log(),y.length&&F(`Run ${b("infernoflow team-sync push")} to share your changes`),r.length&&F(`Run ${b("infernoflow team-sync pull")} to get team changes`)}console.log()}async function X(t){const n=t.slice(1),e=n.includes("--json"),s=n.includes("--force"),c=n.indexOf("--branch"),d=n.indexOf("--remote"),i=c!==-1?n[c+1]:"inferno-contracts",o=d!==-1?n[d+1]:"origin",l=n.find(h=>!h.startsWith("-"))||"status",f=process.cwd(),u=N.join(f,"inferno");if(e||L("infernoflow team-sync"),!g.existsSync(u)){const h="inferno/ not found \u2014 run: infernoflow init";e&&(console.log(JSON.stringify({ok:!1,error:h})),process.exit(1)),S(h),process.exit(1)}switch(l){case"init":U(f,o,i,u,e);break;case"push":z(f,o,i,u,e,s);break;case"pull":W(f,o,i,u,e,s);break;default:G(f,o,i,u,e);break}}export{X as teamSyncCommand};