@codewithdan/zingit 0.16.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
+
5
+ ### [0.16.1](https://github.com/danwahlin/zingit/compare/v0.16.0...v0.16.1) (2026-01-29)
6
+
7
+
8
+ ### Features
9
+
10
+ * integrate standard-version for versioning and changelog management ([69c9f4e](https://github.com/danwahlin/zingit/commit/69c9f4ee5a025d845c4ea6bfb9185c8cee306a2f))
11
+
12
+ ## [0.16.0](https://github.com/danwahlin/zingit/compare/v0.15.0...v0.16.0) (2026-01-28)
13
+
14
+ ### Bug Fixes
15
+
16
+ * **security**: Fix command injection vulnerability in git operations ([server/src/services/git-manager.ts](server/src/services/git-manager.ts))
17
+ * Replaced `execAsync` with `execFileAsync` using array arguments to prevent shell injection
18
+ * Affected commands: git diff, git reset --hard
19
+ * **security**: Fix XSS vulnerability in agent response rendering ([client/src/components/agent-response-panel.ts](client/src/components/agent-response-panel.ts))
20
+ * Implemented safe Lit templating instead of `.innerHTML`
21
+ * All user content is now automatically HTML-escaped by Lit's template system
22
+ * Markdown formatting (bold, italic, code) preserved while preventing script injection
23
+
24
+ ### Features
25
+
26
+ * Add 'prompts' keyword to package.json for improved discoverability
@@ -1442,7 +1442,7 @@
1442
1442
  <div class="spinner"></div>
1443
1443
  <div>Waiting for response${A}...</div>
1444
1444
  </div>
1445
- `}return $`<div class="text-block" style="color: #6b7280;">Add annotations or type a message below to get started.</div>`}return this._parseContent(this.content).map(A=>"code"===A.type?$`<div class="code-block">${A.content}</div>`:this._renderTextAsSteps(A.content))}_renderTextAsSteps(A){const e=A.split(/(?<=\.)\s+(?=[A-Z][a-z])|(?=\n##\s)/).map(A=>A.trim()).filter(A=>A.length>0);return 0===e.length?$``:e.map(A=>{const e=this._classifyStep(A),t=this._parseMarkdown(A);return $`<div class="action-step ${e}" .innerHTML=${t}></div>`})}_parseMarkdown(A){return A.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,"<em>$1</em>").replace(/`([^`]+)`/g,'<code style="background: rgba(59, 130, 246, 0.15); padding: 2px 6px; border-radius: 3px; font-family: monospace;">$1</code>').replace(/\n/g,"<br>")}_classifyStep(A){const e=A.toLowerCase();return e.startsWith("let me check")||e.startsWith("let me look")||e.startsWith("let me run")||e.startsWith("let me also")||e.startsWith("looking at")||e.startsWith("checking")?"check":e.startsWith("added")||e.startsWith("updated")||e.startsWith("created")||e.startsWith("removed")||e.startsWith("fixed")||e.startsWith("changed")?"result":e.startsWith("i don't see")||e.startsWith("i can see")||e.startsWith("there's no")||e.startsWith("there is no")||e.startsWith("the ")||e.startsWith("it's possible")||e.startsWith("perhaps")||e.startsWith("however")?"observation":""}_stopPropagation(A){A.stopPropagation()}_handleClose(A){A.preventDefault(),A.stopPropagation(),this.dispatchEvent(new CustomEvent("close",{bubbles:!0,composed:!0}))}_handleStop(A){A.preventDefault(),A.stopPropagation(),this.dispatchEvent(new CustomEvent("stop",{bubbles:!0,composed:!0}))}_handleRefresh(){window.location.reload()}_handleInputChange(A){this.followUpMessage=A.target.value}_handleInputKeydown(A){"Enter"!==A.key||A.shiftKey||(A.preventDefault(),this._handleSendFollowUp())}_handleSendFollowUp(){this.followUpMessage.trim()&&!this.processing&&(this.dispatchEvent(new CustomEvent("followup",{bubbles:!0,composed:!0,detail:{message:this.followUpMessage}})),this.followUpMessage="")}};$a.styles=o`
1445
+ `}return $`<div class="text-block" style="color: #6b7280;">Add annotations or type a message below to get started.</div>`}return this._parseContent(this.content).map(A=>"code"===A.type?$`<div class="code-block">${A.content}</div>`:this._renderTextAsSteps(A.content))}_renderTextAsSteps(A){const e=A.split(/(?<=\.)\s+(?=[A-Z][a-z])|(?=\n##\s)/).map(A=>A.trim()).filter(A=>A.length>0);return 0===e.length?$``:e.map(A=>{const e=this._classifyStep(A);return $`<div class="action-step ${e}">${this._parseMarkdownToTemplate(A)}</div>`})}_parseMarkdownToTemplate(A){const e=[];let t=0;const n=/(\*\*(.+?)\*\*)|(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|`([^`]+)`/g;let s;for(;null!==(s=n.exec(A));)s.index>t&&e.push(this._textWithLineBreaks(A.slice(t,s.index))),s[2]?e.push($`<strong>${s[2]}</strong>`):s[3]?e.push($`<em>${s[3]}</em>`):s[4]&&e.push($`<code style="background: rgba(59, 130, 246, 0.15); padding: 2px 6px; border-radius: 3px; font-family: monospace;">${s[4]}</code>`),t=s.index+s[0].length;return t<A.length&&e.push(this._textWithLineBreaks(A.slice(t))),e}_textWithLineBreaks(A){const e=A.split("\n"),t=[];return e.forEach((A,n)=>{t.push(A),n<e.length-1&&t.push($`<br>`)}),t}_classifyStep(A){const e=A.toLowerCase();return e.startsWith("let me check")||e.startsWith("let me look")||e.startsWith("let me run")||e.startsWith("let me also")||e.startsWith("looking at")||e.startsWith("checking")?"check":e.startsWith("added")||e.startsWith("updated")||e.startsWith("created")||e.startsWith("removed")||e.startsWith("fixed")||e.startsWith("changed")?"result":e.startsWith("i don't see")||e.startsWith("i can see")||e.startsWith("there's no")||e.startsWith("there is no")||e.startsWith("the ")||e.startsWith("it's possible")||e.startsWith("perhaps")||e.startsWith("however")?"observation":""}_stopPropagation(A){A.stopPropagation()}_handleClose(A){A.preventDefault(),A.stopPropagation(),this.dispatchEvent(new CustomEvent("close",{bubbles:!0,composed:!0}))}_handleStop(A){A.preventDefault(),A.stopPropagation(),this.dispatchEvent(new CustomEvent("stop",{bubbles:!0,composed:!0}))}_handleRefresh(){window.location.reload()}_handleInputChange(A){this.followUpMessage=A.target.value}_handleInputKeydown(A){"Enter"!==A.key||A.shiftKey||(A.preventDefault(),this._handleSendFollowUp())}_handleSendFollowUp(){this.followUpMessage.trim()&&!this.processing&&(this.dispatchEvent(new CustomEvent("followup",{bubbles:!0,composed:!0,detail:{message:this.followUpMessage}})),this.followUpMessage="")}};$a.styles=o`
1446
1446
  :host {
1447
1447
  display: block;
1448
1448
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codewithdan/zingit",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "AI-powered UI annotation tool - point, annotate, and let AI fix it",
5
5
  "type": "module",
6
6
  "engines": {
@@ -15,6 +15,7 @@
15
15
  "client/dist",
16
16
  "README.md",
17
17
  "AGENTS.md",
18
+ "CHANGELOG.md",
18
19
  "LICENSE"
19
20
  ],
20
21
  "main": "client/dist/zingit-client.js",
@@ -29,7 +30,7 @@
29
30
  "test": "cd client && npm run test",
30
31
  "test:watch": "cd client && npm run test:watch",
31
32
  "test:ui": "cd client && npm run test:ui",
32
- "release": "npm version minor && npm run build && npm publish --access public"
33
+ "release": "standard-version && npm run build && npm publish --access public"
33
34
  },
34
35
  "keywords": [
35
36
  "ui",
@@ -69,6 +70,7 @@
69
70
  "@types/ws": "^8.18.1",
70
71
  "concurrently": "^9.1.2",
71
72
  "gh-pages": "^6.3.0",
73
+ "standard-version": "^9.5.0",
72
74
  "tsx": "^4.21.0",
73
75
  "typescript": "^5.9.3"
74
76
  }
@@ -145,7 +145,7 @@ export class GitManager {
145
145
  // Get list of changed files since checkpoint
146
146
  let diffStat = '';
147
147
  try {
148
- const result = await execAsync(`git diff --name-status ${checkpoint.commitHash}`, {
148
+ const result = await execFileAsync('git', ['diff', '--name-status', checkpoint.commitHash], {
149
149
  cwd: this.projectDir,
150
150
  });
151
151
  diffStat = result.stdout;
@@ -166,7 +166,7 @@ export class GitManager {
166
166
  let linesAdded = 0;
167
167
  let linesRemoved = 0;
168
168
  try {
169
- const { stdout: numstat } = await execAsync(`git diff --numstat ${checkpoint.commitHash} -- "${filePath}"`, { cwd: this.projectDir });
169
+ const { stdout: numstat } = await execFileAsync('git', ['diff', '--numstat', checkpoint.commitHash, '--', filePath], { cwd: this.projectDir });
170
170
  const parts = numstat.trim().split('\t');
171
171
  linesAdded = parseInt(parts[0]) || 0;
172
172
  linesRemoved = parseInt(parts[1]) || 0;
@@ -222,7 +222,7 @@ export class GitManager {
222
222
  throw new GitManagerError('Checkpoint is not in applied state', 'INVALID_CHECKPOINT_STATE');
223
223
  }
224
224
  // Reset to the checkpoint's original commit
225
- await execAsync(`git reset --hard ${checkpoint.commitHash}`, { cwd: this.projectDir });
225
+ await execFileAsync('git', ['reset', '--hard', checkpoint.commitHash], { cwd: this.projectDir });
226
226
  // Get files that were reverted
227
227
  const filesReverted = [];
228
228
  // We could compute this but for now just return empty - the checkpoint has the info
@@ -245,7 +245,7 @@ export class GitManager {
245
245
  throw new GitManagerError(`Checkpoint not found: ${checkpointId}`, 'CHECKPOINT_NOT_FOUND');
246
246
  }
247
247
  // Reset to that commit
248
- await execAsync(`git reset --hard ${checkpoint.commitHash}`, { cwd: this.projectDir });
248
+ await execFileAsync('git', ['reset', '--hard', checkpoint.commitHash], { cwd: this.projectDir });
249
249
  // Mark all checkpoints after this one as reverted
250
250
  const checkpointIndex = history.checkpoints.findIndex((c) => c.id === checkpointId);
251
251
  for (let i = checkpointIndex + 1; i < history.checkpoints.length; i++) {