skillwiki 0.10.26 → 0.10.27

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.
@@ -147,6 +147,270 @@ platform_job_status() {
147
147
  printf '{"enabled": %s, "running": %s, "last_exit": %d}\n' "$_enabled" "$_running" "$_last_exit"
148
148
  }
149
149
 
150
+ # macOS LaunchAgent plist validation. Callers inspect the non-empty
151
+ # PLATFORM_LAUNCHD_PLIST_REASON when this returns non-zero. The generated
152
+ # vault-sync units are XML plists, but use plutil to canonicalize a valid
153
+ # binary plist before applying the same structural checks. The validated first
154
+ # argument is exported for installer-only executable preflight warnings.
155
+ PLATFORM_LAUNCHD_PLIST_REASON=""
156
+ PLATFORM_LAUNCHD_PLIST_PROGRAM=""
157
+
158
+ platform_launchd_plist_validate() {
159
+ _platform_expected_label="${1:-}"
160
+ _platform_plist="${2:-}"
161
+ _platform_xml=""
162
+ _platform_fields=""
163
+ _platform_document_kind=""
164
+ _platform_label_kind=""
165
+ _platform_program_kind=""
166
+ _platform_actual_label=""
167
+ _platform_program=""
168
+ PLATFORM_LAUNCHD_PLIST_REASON=""
169
+ PLATFORM_LAUNCHD_PLIST_PROGRAM=""
170
+
171
+ if [ -z "$_platform_expected_label" ]; then
172
+ PLATFORM_LAUNCHD_PLIST_REASON="expected Label is empty"
173
+ return 1
174
+ fi
175
+ if [ -z "$_platform_plist" ] || [ ! -f "$_platform_plist" ]; then
176
+ PLATFORM_LAUNCHD_PLIST_REASON="plist file missing: ${_platform_plist:-unknown}"
177
+ return 1
178
+ fi
179
+ if [ ! -r "$_platform_plist" ]; then
180
+ PLATFORM_LAUNCHD_PLIST_REASON="plist file is not readable: $_platform_plist"
181
+ return 1
182
+ fi
183
+
184
+ if command -v plutil >/dev/null 2>&1; then
185
+ if ! plutil -lint "$_platform_plist" >/dev/null 2>&1; then
186
+ PLATFORM_LAUNCHD_PLIST_REASON="plutil -lint failed"
187
+ return 1
188
+ fi
189
+ if ! _platform_xml="$(plutil -convert xml1 -o - "$_platform_plist" 2>/dev/null)"; then
190
+ PLATFORM_LAUNCHD_PLIST_REASON="plutil could not render plist as XML"
191
+ return 1
192
+ fi
193
+ else
194
+ # Keep status useful on non-macOS CI hosts that lack plutil. This fallback
195
+ # intentionally supports structural XML only; macOS uses plutil above for
196
+ # syntactic validation and binary-plist conversion.
197
+ _platform_xml="$(cat "$_platform_plist")"
198
+ fi
199
+
200
+ # Parse the root plist dictionary structurally rather than searching for
201
+ # text. In particular, a Label-like string inside ProgramArguments must not
202
+ # satisfy Label validation, and ProgramArguments[0] must actually be a
203
+ # non-empty <string>, not merely a scalar which plutil can render as raw.
204
+ _platform_fields="$(
205
+ printf '%s\n' "$_platform_xml" | awk '
206
+ BEGIN {
207
+ label_kind = "missing"
208
+ program_kind = "missing"
209
+ }
210
+ function trim(value) {
211
+ sub(/^[[:space:]]+/, "", value)
212
+ sub(/[[:space:]]+$/, "", value)
213
+ return value
214
+ }
215
+ function string_value(value) {
216
+ sub(/^[[:space:]]*<string>/, "", value)
217
+ sub(/<\/string>[[:space:]]*$/, "", value)
218
+ return value
219
+ }
220
+ function invalid() {
221
+ invalid_token = 1
222
+ }
223
+ function open_container(kind) {
224
+ if (!plist_seen || plist_closed) {
225
+ invalid()
226
+ return
227
+ }
228
+ if (container_depth == 0) {
229
+ if (kind != "dict" || root_dict_seen) {
230
+ invalid()
231
+ return
232
+ }
233
+ root_dict_seen = 1
234
+ }
235
+ container_depth++
236
+ container_stack[container_depth] = kind
237
+ if (kind == "dict") dict_depth++
238
+ }
239
+ function close_container(kind) {
240
+ if (container_depth == 0 || container_stack[container_depth] != kind) {
241
+ invalid()
242
+ return
243
+ }
244
+ if (kind == "dict") {
245
+ if (dict_depth == 1) root_dict_closed = 1
246
+ dict_depth--
247
+ }
248
+ delete container_stack[container_depth]
249
+ container_depth--
250
+ }
251
+ function process(line) {
252
+ line = trim(line)
253
+ if (line == "") return
254
+ if (line ~ /^<!--.*-->$/) return
255
+ if (line ~ /^<\?xml[[:space:]][^>]*\?>$/) {
256
+ if (plist_seen || xml_declaration_seen) invalid()
257
+ xml_declaration_seen = 1
258
+ return
259
+ }
260
+ if (line ~ /^<!DOCTYPE[[:space:]].*>$/) {
261
+ if (plist_seen || doctype_seen) invalid()
262
+ doctype_seen = 1
263
+ return
264
+ }
265
+ if (line ~ /^<plist([[:space:]][^>]*)?>$/) {
266
+ if (plist_seen || plist_closed || container_depth != 0 || root_dict_seen) {
267
+ invalid()
268
+ } else {
269
+ plist_seen = 1
270
+ }
271
+ return
272
+ }
273
+ if (line == "</plist>") {
274
+ if (!plist_seen || plist_closed || !root_dict_closed || container_depth != 0) {
275
+ invalid()
276
+ } else {
277
+ plist_closed = 1
278
+ }
279
+ return
280
+ }
281
+
282
+ if (label_wait) {
283
+ if (line ~ /^<string>[^<]*<\/string>$/) {
284
+ label_value = string_value(line)
285
+ label_kind = "string"
286
+ label_wait = 0
287
+ return
288
+ }
289
+ label_kind = "not-string"
290
+ label_wait = 0
291
+ }
292
+
293
+ if (program_wait_array) {
294
+ if (line == "<array>") {
295
+ program_wait_array = 0
296
+ program_wait_first = 1
297
+ open_container("array")
298
+ return
299
+ }
300
+ program_kind = "not-array"
301
+ program_wait_array = 0
302
+ }
303
+
304
+ if (program_wait_first) {
305
+ if (line ~ /^<string>[^<]*<\/string>$/) {
306
+ program_value = string_value(line)
307
+ program_kind = (program_value == "" ? "empty" : "string")
308
+ program_wait_first = 0
309
+ return
310
+ }
311
+ program_kind = (line == "</array>" ? "missing" : "not-string")
312
+ program_wait_first = 0
313
+ }
314
+
315
+ if (line == "<dict>") {
316
+ open_container("dict")
317
+ return
318
+ }
319
+ if (line == "</dict>") {
320
+ close_container("dict")
321
+ return
322
+ }
323
+ if (line == "<array>") {
324
+ open_container("array")
325
+ return
326
+ }
327
+ if (line == "</array>") {
328
+ close_container("array")
329
+ return
330
+ }
331
+ if (line ~ /^<key>Label<\/key>$/ && dict_depth == 1 && container_depth == 1 && label_kind == "missing") {
332
+ label_wait = 1
333
+ return
334
+ }
335
+ if (line ~ /^<key>ProgramArguments<\/key>$/ && dict_depth == 1 && container_depth == 1 && program_kind == "missing") {
336
+ program_wait_array = 1
337
+ return
338
+ }
339
+ if (line ~ /^<key>[^<]*<\/key>$/ ||
340
+ line ~ /^<string>[^<]*<\/string>$/ ||
341
+ line ~ /^<integer>[^<]*<\/integer>$/ ||
342
+ line ~ /^<real>[^<]*<\/real>$/ ||
343
+ line ~ /^<date>[^<]*<\/date>$/ ||
344
+ line ~ /^<data>[^<]*<\/data>$/ ||
345
+ line == "<true/>" || line == "<false/>") {
346
+ if (!plist_seen || plist_closed || container_depth == 0) invalid()
347
+ return
348
+ }
349
+ invalid()
350
+ }
351
+ {
352
+ content = $0
353
+ gsub(/></, ">\n<", content)
354
+ count = split(content, chunks, "\n")
355
+ for (chunk_index = 1; chunk_index <= count; chunk_index++) process(chunks[chunk_index])
356
+ }
357
+ END {
358
+ if (label_wait && label_kind == "missing") label_kind = "not-string"
359
+ if (program_wait_array) program_kind = "not-array"
360
+ if (program_wait_first) program_kind = "missing"
361
+ document_kind = (plist_seen && plist_closed && root_dict_seen && root_dict_closed &&
362
+ dict_depth == 0 && container_depth == 0 && !invalid_token ? "valid" : "invalid")
363
+ printf "DOCUMENT_KIND=%s\n", document_kind
364
+ printf "LABEL_KIND=%s\n", label_kind
365
+ printf "LABEL_VALUE=%s\n", label_value
366
+ printf "PROGRAM_KIND=%s\n", program_kind
367
+ printf "PROGRAM_VALUE=%s\n", program_value
368
+ }
369
+ '
370
+ )"
371
+ _platform_document_kind="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^DOCUMENT_KIND=/, "") { print; exit }')"
372
+ _platform_label_kind="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^LABEL_KIND=/, "") { print; exit }')"
373
+ _platform_actual_label="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^LABEL_VALUE=/, "") { print; exit }')"
374
+ _platform_program_kind="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^PROGRAM_KIND=/, "") { print; exit }')"
375
+ _platform_program="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^PROGRAM_VALUE=/, "") { print; exit }')"
376
+
377
+ if [ "$_platform_document_kind" != "valid" ]; then
378
+ PLATFORM_LAUNCHD_PLIST_REASON="plist XML structure is invalid"
379
+ return 1
380
+ fi
381
+ if [ "$_platform_label_kind" != "string" ]; then
382
+ PLATFORM_LAUNCHD_PLIST_REASON="Label missing or is not a string"
383
+ return 1
384
+ fi
385
+
386
+ if [ "$_platform_actual_label" != "$_platform_expected_label" ]; then
387
+ PLATFORM_LAUNCHD_PLIST_REASON="Label is '${_platform_actual_label:-missing}', expected '$_platform_expected_label'"
388
+ return 1
389
+ fi
390
+ case "$_platform_program_kind" in
391
+ missing)
392
+ PLATFORM_LAUNCHD_PLIST_REASON="ProgramArguments[0] missing"
393
+ return 1
394
+ ;;
395
+ empty)
396
+ PLATFORM_LAUNCHD_PLIST_REASON="ProgramArguments[0] is empty"
397
+ return 1
398
+ ;;
399
+ string)
400
+ ;;
401
+ *)
402
+ PLATFORM_LAUNCHD_PLIST_REASON="ProgramArguments[0] must be a string"
403
+ return 1
404
+ ;;
405
+ esac
406
+ if [ -z "$_platform_program" ]; then
407
+ PLATFORM_LAUNCHD_PLIST_REASON="ProgramArguments[0] missing"
408
+ return 1
409
+ fi
410
+ PLATFORM_LAUNCHD_PLIST_PROGRAM="$_platform_program"
411
+ return 0
412
+ }
413
+
150
414
  # Feature prerequisite check:
151
415
  # exit 1 with message if not available
152
416
  platform_require() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.26",
3
+ "version": "0.10.27",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "skillwiki": "dist/cli.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.26",
3
+ "version": "0.10.27",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 19 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.26",
3
+ "version": "0.10.27",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 19 prompt-only skills backed by the deterministic skillwiki CLI.",
5
5
  "author": {
6
6
  "name": "karlorz",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.10.26",
3
+ "version": "0.10.27",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",