skillwiki 0.10.109 → 0.10.110

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.
@@ -3,11 +3,11 @@ import {
3
3
  ROOT_INDEX_SECTION_ORDER,
4
4
  atomicWriteText,
5
5
  buildRootIndexUniverse
6
- } from "./chunk-HMHMCPCE.js";
6
+ } from "./chunk-WPJHDRTP.js";
7
7
  import {
8
8
  err,
9
9
  ok
10
- } from "./chunk-GJC2CI3W.js";
10
+ } from "./chunk-R2DJ7IJ5.js";
11
11
 
12
12
  // src/utils/index-projection.ts
13
13
  import { readFile } from "fs/promises";
@@ -8,7 +8,7 @@ import {
8
8
  readLogEvents,
9
9
  resolveExistingRegularFileInsideVault,
10
10
  writeLogEvent
11
- } from "./chunk-JCDAEZVD.js";
11
+ } from "./chunk-5MV5SSRM.js";
12
12
  import {
13
13
  ExitCode,
14
14
  RawSourceSchema,
@@ -19,7 +19,7 @@ import {
19
19
  scanSensitiveContent,
20
20
  scanVault,
21
21
  splitFrontmatter
22
- } from "./chunk-GJC2CI3W.js";
22
+ } from "./chunk-R2DJ7IJ5.js";
23
23
 
24
24
  // src/commands/sources.ts
25
25
  import { readFile as readFile4 } from "fs/promises";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  atomicWriteText,
4
4
  buildRootIndexUniverse
5
- } from "./chunk-HMHMCPCE.js";
5
+ } from "./chunk-WPJHDRTP.js";
6
6
  import {
7
7
  authorizeRawOperation,
8
8
  buildSourceReferenceIndex,
@@ -22,7 +22,7 @@ import {
22
22
  resolveExistingRegularFileInsideVault,
23
23
  stripFencedBlocks,
24
24
  writeLogEvent
25
- } from "./chunk-JCDAEZVD.js";
25
+ } from "./chunk-5MV5SSRM.js";
26
26
  import {
27
27
  ExitCode,
28
28
  FleetManifestSchema,
@@ -30,6 +30,7 @@ import {
30
30
  extractFrontmatter,
31
31
  filterGitIgnoredRelativePaths,
32
32
  getErrorMessage,
33
+ isClipOutboxKey,
33
34
  isVaultSyncKey,
34
35
  mapWithConcurrency,
35
36
  ok,
@@ -41,7 +42,7 @@ import {
41
42
  scanVault,
42
43
  splitFrontmatter,
43
44
  vaultIoConcurrency
44
- } from "./chunk-GJC2CI3W.js";
45
+ } from "./chunk-R2DJ7IJ5.js";
45
46
 
46
47
  // src/utils/home.ts
47
48
  import { homedir } from "os";
@@ -104,7 +105,7 @@ function parseDotenvText(text) {
104
105
  if (eq <= 0) continue;
105
106
  const key = line.slice(0, eq).trim();
106
107
  const value = line.slice(eq + 1).trim();
107
- if (!_whitelist.has(key) && !isValidWikiProfileKey(key) && !isVaultSyncKey(key)) continue;
108
+ if (!_whitelist.has(key) && !isValidWikiProfileKey(key) && !isVaultSyncKey(key) && !isClipOutboxKey(key)) continue;
108
109
  if (value.length === 0) continue;
109
110
  out[key] = value;
110
111
  }
@@ -419,6 +419,106 @@ function parseVaultSyncKeyValue(key, value) {
419
419
  const first = parsed.error.issues[0];
420
420
  return { ok: false, message: first?.message ?? "invalid value" };
421
421
  }
422
+ var clipBool = z.enum(["true", "false"]);
423
+ var clipIntString = (min, max) => z.string().regex(/^[0-9]+$/, "must be a decimal integer").refine((s) => {
424
+ const n = Number(s);
425
+ return Number.isInteger(n) && n >= min && n <= max;
426
+ }, { message: `must be an integer ${min}\u2013${max}` });
427
+ var clipHttpsUrl = z.string().min(1).refine((s) => {
428
+ try {
429
+ const u = new URL(s);
430
+ return u.protocol === "https:" && u.username === "" && u.password === "";
431
+ } catch {
432
+ return false;
433
+ }
434
+ }, { message: "must be an https URL without userinfo" });
435
+ var clipCredentialRef = z.string().min(1).max(128).regex(
436
+ /^[a-zA-Z0-9_.-]+$/,
437
+ "must be a credential reference name, never a secret value"
438
+ );
439
+ var CLIP_IMAGE_TYPES = ["png", "jpeg", "gif", "webp"];
440
+ var CLIP_ALLOWED_PREFIXES = "raw/articles,raw/assets";
441
+ var CLIP_OUTBOX_DEFAULTS = {
442
+ enabled: "false",
443
+ vault_id: "central",
444
+ allowed_prefixes: CLIP_ALLOWED_PREFIXES,
445
+ destination: "seaweed-wiki:cloud/wiki",
446
+ mode: "event-watch",
447
+ poll_fallback_enabled: "true",
448
+ reconcile_seconds: "3600",
449
+ debounce_ms: "500",
450
+ stable_hash_ms: "500",
451
+ max_article_bytes: "2097152",
452
+ max_images: "64",
453
+ max_image_bytes: "20971520",
454
+ max_bundle_bytes: "104857600",
455
+ max_pixels: "40000000",
456
+ download_concurrency: "4",
457
+ image_types: CLIP_IMAGE_TYPES.join(","),
458
+ max_redirects: "5",
459
+ request_timeout_ms: "30000",
460
+ localize_timeout_ms: "120000",
461
+ watcher_label: "com.karlchow.skillwiki-clip-outbox"
462
+ };
463
+ var ClipOutboxConfigSchema = z.object({
464
+ "clip_outbox.enabled": clipBool,
465
+ "clip_outbox.root": absolutePath,
466
+ "clip_outbox.state_root": absolutePath,
467
+ "clip_outbox.mirror_root": absolutePath,
468
+ "clip_outbox.d8_note_root": absolutePath,
469
+ "clip_outbox.d8_note_absent": clipBool,
470
+ "clip_outbox.vault_id": z.literal("central"),
471
+ "clip_outbox.allowed_prefixes": z.string().refine((s) => {
472
+ const parts = s.split(",").map((p) => p.trim()).filter(Boolean).sort();
473
+ return parts.join(",") === "raw/articles,raw/assets";
474
+ }, { message: "must be exactly raw/articles,raw/assets" }),
475
+ "clip_outbox.destination": z.literal("seaweed-wiki:cloud/wiki"),
476
+ "clip_outbox.mode": z.literal("event-watch"),
477
+ "clip_outbox.poll_fallback_enabled": clipBool,
478
+ "clip_outbox.reconcile_seconds": clipIntString(1, 86400),
479
+ "clip_outbox.debounce_ms": clipIntString(100, 5e3),
480
+ "clip_outbox.stable_hash_ms": clipIntString(100, 5e3),
481
+ "clip_outbox.max_article_bytes": clipIntString(1, 2097152),
482
+ "clip_outbox.max_images": clipIntString(0, 64),
483
+ "clip_outbox.max_image_bytes": clipIntString(1, 20971520),
484
+ "clip_outbox.max_bundle_bytes": clipIntString(1, 104857600),
485
+ "clip_outbox.max_pixels": clipIntString(1, 4e7),
486
+ "clip_outbox.download_concurrency": clipIntString(1, 4),
487
+ "clip_outbox.image_types": z.string().refine((s) => {
488
+ const parts = s.split(",").map((p) => p.trim()).filter(Boolean);
489
+ if (parts.length === 0) return false;
490
+ const allowed = new Set(CLIP_IMAGE_TYPES);
491
+ const seen = /* @__PURE__ */ new Set();
492
+ for (const p of parts) {
493
+ if (!allowed.has(p) || seen.has(p)) return false;
494
+ seen.add(p);
495
+ }
496
+ return true;
497
+ }, { message: "must be a unique subset of png,jpeg,gif,webp" }),
498
+ "clip_outbox.max_redirects": clipIntString(0, 5),
499
+ "clip_outbox.request_timeout_ms": clipIntString(1, 3e4),
500
+ "clip_outbox.localize_timeout_ms": clipIntString(1, 12e4),
501
+ "clip_outbox.ready_url": clipHttpsUrl,
502
+ "clip_outbox.s3_credential_ref": clipCredentialRef,
503
+ "clip_outbox.ready_credential_ref": clipCredentialRef,
504
+ "clip_outbox.watcher_label": z.literal("com.karlchow.skillwiki-clip-outbox")
505
+ }).strict();
506
+ var CLIP_OUTBOX_KEYS = Object.keys(ClipOutboxConfigSchema.shape);
507
+ function isClipOutboxKey(key) {
508
+ return CLIP_OUTBOX_KEYS.includes(key);
509
+ }
510
+ function parseClipOutboxKeyValue(key, value) {
511
+ if (!isClipOutboxKey(key)) {
512
+ return { ok: false, message: `unknown clip_outbox key: ${key}` };
513
+ }
514
+ const fieldSchema = ClipOutboxConfigSchema.shape[key];
515
+ const parsed = fieldSchema.safeParse(value);
516
+ if (parsed.success) {
517
+ return { ok: true };
518
+ }
519
+ const first = parsed.error.issues[0];
520
+ return { ok: false, message: first?.message ?? "invalid value" };
521
+ }
422
522
  function detectSchema(fm) {
423
523
  const COMPOUND_TYPES = /* @__PURE__ */ new Set(["lesson", "pattern", "antipattern", "gotcha"]);
424
524
  if (typeof fm.type === "string" && COMPOUND_TYPES.has(fm.type) && "project" in fm) return { schema: "compound" };
@@ -834,6 +934,9 @@ export {
834
934
  FleetManifestSchema,
835
935
  isVaultSyncKey,
836
936
  parseVaultSyncKeyValue,
937
+ CLIP_OUTBOX_DEFAULTS,
938
+ isClipOutboxKey,
939
+ parseClipOutboxKeyValue,
837
940
  detectSchema,
838
941
  isBlockedHost,
839
942
  getErrorMessage,
@@ -8,7 +8,7 @@ import {
8
8
  ok,
9
9
  scanSensitiveContent,
10
10
  scanVault
11
- } from "./chunk-GJC2CI3W.js";
11
+ } from "./chunk-R2DJ7IJ5.js";
12
12
 
13
13
  // src/utils/atomic-write.ts
14
14
  import { randomBytes } from "crypto";
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CAPTURE_HYGIENE_CONTRACT,
4
+ CLIP_NOT_CONFIGURED,
4
5
  DEFAULT_CAPTURE_BUDGET,
5
6
  DEFAULT_DIRTY_VOLUME_THRESHOLD,
6
7
  DEFAULT_MCP_URL,
@@ -21,6 +22,7 @@ import {
21
22
  guardProtectedVaultWrite,
22
23
  isEventLedgerPath,
23
24
  isFailedRunStatus,
25
+ loadClipOutboxConfig,
24
26
  mcpAuthFromEnv,
25
27
  measureDirtyVolume,
26
28
  memoryAuthorityTiersRank,
@@ -65,7 +67,7 @@ import {
65
67
  upsertIndexEntry,
66
68
  vectorIndexStatus,
67
69
  withMcpClient
68
- } from "./chunk-LDEZQXH6.js";
70
+ } from "./chunk-6X6PPQNI.js";
69
71
  import {
70
72
  normalizeDistTag,
71
73
  readCache,
@@ -78,7 +80,7 @@ import {
78
80
  UNMANAGED_START,
79
81
  renderRootIndex,
80
82
  writeRootIndexProjection
81
- } from "./chunk-DTTKCCQ4.js";
83
+ } from "./chunk-KX5Y3GSX.js";
82
84
  import {
83
85
  FLEET_REL_PATH,
84
86
  REDACTED_MALFORMED_REFERENCE,
@@ -171,12 +173,12 @@ import {
171
173
  supersedeStaleReviewRequiredJournals,
172
174
  taxonomyCommentForPage,
173
175
  writeDotenv
174
- } from "./chunk-FYAKMULW.js";
176
+ } from "./chunk-QU2CKXM4.js";
175
177
  import {
176
178
  assertTargetInsideVault,
177
179
  atomicWriteText,
178
180
  prepareTypedPage
179
- } from "./chunk-HMHMCPCE.js";
181
+ } from "./chunk-WPJHDRTP.js";
180
182
  import {
181
183
  applySourceDisposition,
182
184
  decodeSourceActionApproval,
@@ -184,7 +186,7 @@ import {
184
186
  inventorySources,
185
187
  planSourceDisposition,
186
188
  runSourcesPending
187
- } from "./chunk-QU2IXZRN.js";
189
+ } from "./chunk-NA3TZ4JN.js";
188
190
  import {
189
191
  authorizeRawOperation,
190
192
  buildSourceReferenceIndex,
@@ -201,7 +203,7 @@ import {
201
203
  resolveExistingRegularFileInsideVault,
202
204
  validateLogEvent,
203
205
  writeLogEvent
204
- } from "./chunk-JCDAEZVD.js";
206
+ } from "./chunk-5MV5SSRM.js";
205
207
  import {
206
208
  ExitCode,
207
209
  MetaSchema,
@@ -222,7 +224,7 @@ import {
222
224
  scanVault,
223
225
  splitFrontmatter,
224
226
  vaultIoConcurrency
225
- } from "./chunk-GJC2CI3W.js";
227
+ } from "./chunk-R2DJ7IJ5.js";
226
228
 
227
229
  // src/cli.ts
228
230
  import { join as join45 } from "path";
@@ -1288,7 +1290,7 @@ gitignore: ${gi.written ? "merged" : "unchanged"} (write-gitignore only)`,
1288
1290
  skipEnv ? "env: skipped" : `env: ${envWritten}`,
1289
1291
  input.noGitignore ? "gitignore: skipped" : `gitignore: ${gitignoreWritten ? "written" : "unchanged"}`,
1290
1292
  `web clipper template: ${WEB_CLIPPER_TEMPLATE_REL}`,
1291
- "web clipper import: open Obsidian Web Clipper Settings and import the JSON in each browser profile"
1293
+ "web clipper import: create ~/Obsidian/clip-outbox (not ~/wiki), import the JSON in each browser profile, select that outbox vault, keep Add to Obsidian"
1292
1294
  ].join("\n");
1293
1295
  if (created.length > 0) {
1294
1296
  appendLastOp(target, {
@@ -3587,6 +3589,30 @@ async function runHealth(input) {
3587
3589
  return { exitCode, result: ok(report) };
3588
3590
  }
3589
3591
 
3592
+ // src/commands/clip-outbox.ts
3593
+ async function runClipOutboxStatus(home) {
3594
+ const resolved = await loadClipOutboxConfig(home);
3595
+ if (!resolved.ok) {
3596
+ return {
3597
+ exitCode: ExitCode.INVALID_CONFIG_VALUE,
3598
+ result: err("INVALID_CONFIG_VALUE", { message: resolved.message })
3599
+ };
3600
+ }
3601
+ if (!resolved.enabled) {
3602
+ return {
3603
+ exitCode: ExitCode.OK,
3604
+ result: ok({ enabled: false, humanHint: CLIP_NOT_CONFIGURED })
3605
+ };
3606
+ }
3607
+ return {
3608
+ exitCode: ExitCode.OK,
3609
+ result: ok({
3610
+ enabled: true,
3611
+ humanHint: `clip-outbox enabled root=${resolved.config.root} mode=${resolved.config.mode}`
3612
+ })
3613
+ };
3614
+ }
3615
+
3590
3616
  // src/commands/connect.ts
3591
3617
  import { chmod, mkdir as mkdir5, readFile as readFile6, stat as stat3 } from "fs/promises";
3592
3618
  import { join as join18 } from "path";
@@ -4107,7 +4133,7 @@ ${fmRewritten}
4107
4133
  }
4108
4134
  let indexUpdated = false;
4109
4135
  if (!isRaw) {
4110
- const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-ESBJOBS7.js");
4136
+ const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-6XGESSHV.js");
4111
4137
  const before = await readFile8(join20(input.vault, "index.md"), "utf8").catch(() => "");
4112
4138
  const fullTarget = relPath.replace(/\.md$/, "");
4113
4139
  const bare = fullTarget.split("/").pop() ?? fullTarget;
@@ -4230,7 +4256,7 @@ async function runRemove(input) {
4230
4256
  if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
4231
4257
  const { readFile: readFile24 } = await import("fs/promises");
4232
4258
  const { join: pathJoin } = await import("path");
4233
- const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-ESBJOBS7.js");
4259
+ const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-6XGESSHV.js");
4234
4260
  const before = await readFile24(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
4235
4261
  const fullTarget = relPath.replace(/\.md$/, "");
4236
4262
  const bare = fullTarget.split("/").pop() ?? fullTarget;
@@ -9761,11 +9787,20 @@ async function measureAuthoritativeLiveDrift(liveVault, projection) {
9761
9787
 
9762
9788
  // src/copy-status/copy-status.ts
9763
9789
  function liveRecord(p) {
9790
+ if (p.writable === false) {
9791
+ return {
9792
+ state: "blocked",
9793
+ reachable: p.reachable,
9794
+ writable: false,
9795
+ blocked_reason: "s3_write_probe_failed",
9796
+ detail: p.detail ?? "S3 write probe failed"
9797
+ };
9798
+ }
9764
9799
  if (p.unknown || p.reachable === void 0) {
9765
9800
  return { state: "unknown", reachable: p.reachable, detail: p.detail ?? "S3/MCP unmeasured" };
9766
9801
  }
9767
9802
  if (p.reachable) {
9768
- return { state: "ok", reachable: true, detail: p.detail ?? "S3 reachable" };
9803
+ return { state: "ok", reachable: true, ...p.writable === true ? { writable: true } : {}, detail: p.detail ?? "S3 reachable" };
9769
9804
  }
9770
9805
  return { state: "unknown", reachable: false, detail: p.detail ?? "S3 unreachable" };
9771
9806
  }
@@ -9937,6 +9972,14 @@ function defaultCopyStatusDeps(input) {
9937
9972
  };
9938
9973
  return {
9939
9974
  probeLive() {
9975
+ if (input.s3Writable === false) {
9976
+ return {
9977
+ reachable: input.s3Ok,
9978
+ writable: false,
9979
+ detail: `MCP S3 write probe failed${input.s3WritableError ? ` (${input.s3WritableError})` : ""}`
9980
+ };
9981
+ }
9982
+ if (input.s3Ok === true && input.s3Writable === true) return { reachable: true, writable: true, detail: "MCP S3 readable and writable" };
9940
9983
  if (input.s3Ok === true) return { reachable: true, detail: "MCP S3 ok" };
9941
9984
  if (input.s3Ok === false) return { reachable: false, detail: "MCP S3 not ok" };
9942
9985
  const remote = resolveWikiS3Remote({ home: input.home });
@@ -11212,7 +11255,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
11212
11255
  if (dirty) {
11213
11256
  return emit(dirty, void 0, { postCommit: false });
11214
11257
  }
11215
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-MAIGWFQJ.js");
11258
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-JPOBVVSU.js");
11216
11259
  const run = await runManagedWriteTransaction2({
11217
11260
  vault,
11218
11261
  command,
@@ -11856,6 +11899,8 @@ program.command("doctor").description("diagnose skillwiki setup issues").option(
11856
11899
  checkMcp: !!opts.checkMcp,
11857
11900
  env: process.env
11858
11901
  })));
11902
+ var clipOutboxCmd = program.command("clip-outbox").description("dedicated file-level web-clip outbox");
11903
+ clipOutboxCmd.command("status").description("show whether the clip-outbox channel is configured").action(async () => emit(await runClipOutboxStatus(resolveSkillwikiHome())));
11859
11904
  program.command("connect").description(CONNECT_DESCRIPTION).option("--from-file <path>", "chat-attached env file (not Drive / \u4E91\u76D8)").option("--from-stdin", "read env file from stdin (no echo)").option("--dry-run", "validate without writing ~/.skillwiki/.env").option("--force", "overwrite a different existing token").action(async (opts) => emit(await runConnect({
11860
11905
  home: resolveSkillwikiHome(),
11861
11906
  fromFile: opts.fromFile,
@@ -4,9 +4,9 @@ import {
4
4
  UNMANAGED_START,
5
5
  renderRootIndex,
6
6
  writeRootIndexProjection
7
- } from "./chunk-DTTKCCQ4.js";
8
- import "./chunk-HMHMCPCE.js";
9
- import "./chunk-GJC2CI3W.js";
7
+ } from "./chunk-KX5Y3GSX.js";
8
+ import "./chunk-WPJHDRTP.js";
9
+ import "./chunk-R2DJ7IJ5.js";
10
10
  export {
11
11
  UNMANAGED_END,
12
12
  UNMANAGED_START,
@@ -6,10 +6,10 @@ import {
6
6
  runManagedWritePeerGate,
7
7
  runManagedWritePreflight,
8
8
  runManagedWriteTransaction
9
- } from "./chunk-FYAKMULW.js";
10
- import "./chunk-HMHMCPCE.js";
11
- import "./chunk-JCDAEZVD.js";
12
- import "./chunk-GJC2CI3W.js";
9
+ } from "./chunk-QU2CKXM4.js";
10
+ import "./chunk-WPJHDRTP.js";
11
+ import "./chunk-5MV5SSRM.js";
12
+ import "./chunk-R2DJ7IJ5.js";
13
13
  export {
14
14
  DEFAULT_MANAGED_WRITE_WAIT_MS,
15
15
  MANAGED_WRITE_POLL_INTERVAL_MS,
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runSkillwikiMcpStdio
4
- } from "./chunk-LDEZQXH6.js";
4
+ } from "./chunk-6X6PPQNI.js";
5
5
  import "./chunk-7I2TPIV5.js";
6
- import "./chunk-DTTKCCQ4.js";
7
- import "./chunk-FYAKMULW.js";
8
- import "./chunk-HMHMCPCE.js";
9
- import "./chunk-QU2IXZRN.js";
10
- import "./chunk-JCDAEZVD.js";
11
- import "./chunk-GJC2CI3W.js";
6
+ import "./chunk-KX5Y3GSX.js";
7
+ import "./chunk-QU2CKXM4.js";
8
+ import "./chunk-WPJHDRTP.js";
9
+ import "./chunk-NA3TZ4JN.js";
10
+ import "./chunk-5MV5SSRM.js";
11
+ import "./chunk-R2DJ7IJ5.js";
12
12
 
13
13
  // src/mcp-entry.ts
14
14
  runSkillwikiMcpStdio().catch((error) => {
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runSourcesPending
4
+ } from "./chunk-NA3TZ4JN.js";
5
+ import "./chunk-5MV5SSRM.js";
6
+ import "./chunk-R2DJ7IJ5.js";
7
+ export {
8
+ runSourcesPending
9
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.109",
3
+ "version": "0.10.110",
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.109",
3
+ "version": "0.10.110",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 22 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp, skillwiki-connect) backed by the deterministic `skillwiki` CLI.",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.109",
3
+ "version": "0.10.110",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 22 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",
3
- "version": "0.10.109",
3
+ "version": "0.10.110",
4
4
  "description": "Project-aware Karpathy-style knowledge base: 22 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp, skillwiki-connect) backed by HTTP MCP wiki_capture.",
5
5
  "author": {
6
6
  "name": "karlorz"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.10.109",
3
+ "version": "0.10.110",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",
@@ -183,7 +183,7 @@ If the vault has `.WIKI_GIT_FROZEN`:
183
183
  ## Skill Map
184
184
  | Skill | When to Invoke |
185
185
  |-------|----------------|
186
- | `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance |
186
+ | `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows; `wiki_capture` is not clip ingress. |
187
187
  | `wiki-ingest` | Convert URLs, files, or pasted text into typed-knowledge pages |
188
188
  | `wiki-query` | Search typed knowledge by default; work-queue / doctor / MCP diagnose questions use `wiki_context` or `--scope work` / `--scope all`; explicitly requested fresh/raw evidence uses the separate pending channel |
189
189
  | `wiki-lint` | Vault health and lint checks; use `health` for whole-system reports and `lint --summary` for bounded lint buckets |
@@ -29,17 +29,18 @@ None for the first run.
29
29
  `skillwiki init --target <dir> --domain existing --write-gitignore`.
30
30
  6. Report the installed Web Clipper assets at
31
31
  `_Templates/web-clipper/llm-wiki-clippings.json` and
32
- `_Templates/web-clipper/readme.txt`. Tell the user to open Obsidian Web
33
- Clipper Settings, import the JSON, and repeat the import for every browser
34
- profile that should capture into the vault. Re-init preserves existing
35
- customized copies, including short files.
36
- 7. Explain that the generic template preserves `{{content}}`; remote HTTP(S)
37
- images remain external dependencies. An attended local-asset capture may
38
- choose any URL-friendly path under `raw/assets/`, but it must write the asset,
39
- emit an explicit vault-qualified `![[raw/assets/...]]` embed, verify
40
- resolution/preview, and only then finalize the immutable raw note. Also write
41
- a sibling Markdown note (`listings.md`, `note.md`, or a dated `.md`) that
42
- embeds each file. Never use `.txt` as the only index.
32
+ `_Templates/web-clipper/readme.txt`. For the file-level clip channel, create
33
+ or select the dedicated Obsidian vault `~/Obsidian/clip-outbox` (never
34
+ `~/wiki`), with `raw/articles` and `raw/assets`, attachment folder
35
+ `raw/assets`. Tell the user to open Obsidian Web Clipper Settings, import the
36
+ JSON, select that outbox vault, keep **Add to Obsidian**, and repeat the
37
+ import for every browser profile. Re-init preserves existing customized
38
+ copies, including short files. A new package version never silently updates
39
+ browser extension storage.
40
+ 7. Explain that the generic template preserves `{{content}}`. The clip-outbox
41
+ worker localizes required v1 HTTPS images before freeze, writes
42
+ `![[raw/assets/...]]` embeds, and uploads `raw/assets/<capture-id>/note.md`.
43
+ `wiki_capture` is not clip ingress. Never use `.txt` as the only index.
43
44
  8. **Suggest first sources.** Propose 3–5 initial sources (URLs, papers, articles) appropriate to the domain. Prompt the user to provide the first one to ingest, then hand off to wiki-ingest.
44
45
 
45
46
  ## Stop conditions
@@ -183,7 +183,7 @@ If the vault has `.WIKI_GIT_FROZEN`:
183
183
  ## Skill Map
184
184
  | Skill | When to Invoke |
185
185
  |-------|----------------|
186
- | `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance |
186
+ | `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows; `wiki_capture` is not clip ingress. |
187
187
  | `wiki-ingest` | Convert URLs, files, or pasted text into typed-knowledge pages |
188
188
  | `wiki-query` | Search typed knowledge by default; work-queue / doctor / MCP diagnose questions use `wiki_context` or `--scope work` / `--scope all`; explicitly requested fresh/raw evidence uses the separate pending channel |
189
189
  | `wiki-lint` | Vault health and lint checks; use `health` for whole-system reports and `lint --summary` for bounded lint buckets |
@@ -29,17 +29,18 @@ None for the first run.
29
29
  `skillwiki init --target <dir> --domain existing --write-gitignore`.
30
30
  6. Report the installed Web Clipper assets at
31
31
  `_Templates/web-clipper/llm-wiki-clippings.json` and
32
- `_Templates/web-clipper/readme.txt`. Tell the user to open Obsidian Web
33
- Clipper Settings, import the JSON, and repeat the import for every browser
34
- profile that should capture into the vault. Re-init preserves existing
35
- customized copies, including short files.
36
- 7. Explain that the generic template preserves `{{content}}`; remote HTTP(S)
37
- images remain external dependencies. An attended local-asset capture may
38
- choose any URL-friendly path under `raw/assets/`, but it must write the asset,
39
- emit an explicit vault-qualified `![[raw/assets/...]]` embed, verify
40
- resolution/preview, and only then finalize the immutable raw note. Also write
41
- a sibling Markdown note (`listings.md`, `note.md`, or a dated `.md`) that
42
- embeds each file. Never use `.txt` as the only index.
32
+ `_Templates/web-clipper/readme.txt`. For the file-level clip channel, create
33
+ or select the dedicated Obsidian vault `~/Obsidian/clip-outbox` (never
34
+ `~/wiki`), with `raw/articles` and `raw/assets`, attachment folder
35
+ `raw/assets`. Tell the user to open Obsidian Web Clipper Settings, import the
36
+ JSON, select that outbox vault, keep **Add to Obsidian**, and repeat the
37
+ import for every browser profile. Re-init preserves existing customized
38
+ copies, including short files. A new package version never silently updates
39
+ browser extension storage.
40
+ 7. Explain that the generic template preserves `{{content}}`. The clip-outbox
41
+ worker localizes required v1 HTTPS images before freeze, writes
42
+ `![[raw/assets/...]]` embeds, and uploads `raw/assets/<capture-id>/note.md`.
43
+ `wiki_capture` is not clip ingress. Never use `.txt` as the only index.
43
44
  8. **Suggest first sources.** Propose 3–5 initial sources (URLs, papers, articles) appropriate to the domain. Prompt the user to provide the first one to ingest, then hand off to wiki-ingest.
44
45
 
45
46
  ## Stop conditions
@@ -0,0 +1,22 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.karlchow.skillwiki-clip-outbox</string>
7
+ <key>ProgramArguments</key>
8
+ <array>
9
+ <string>/usr/local/bin/skillwiki</string>
10
+ <string>clip-outbox</string>
11
+ <string>watch</string>
12
+ </array>
13
+ <key>KeepAlive</key>
14
+ <true/>
15
+ <key>RunAtLoad</key>
16
+ <true/>
17
+ <key>WatchPaths</key>
18
+ <array>
19
+ <string>/Users/REPLACE/Obsidian/clip-outbox/raw/articles</string>
20
+ </array>
21
+ </dict>
22
+ </plist>
@@ -51,6 +51,6 @@
51
51
  }
52
52
  ],
53
53
  "triggers": [],
54
- "noteNameFormat": "{{date|date:\"YYYY-MM-DD\"}}-{{title|safe_name}}",
54
+ "noteNameFormat": "{{date|date:\"YYYY-MM-DD\"}}-{{title|safe_name}}-{{date|date:\"HHmmss\"}}",
55
55
  "path": "raw/articles"
56
56
  }
@@ -1,16 +1,16 @@
1
1
  SkillWiki Obsidian Web Clipper template
2
2
 
3
- 1. Open Obsidian Web Clipper Settings in the browser profile that will capture this vault.
4
- 2. Import llm-wiki-clippings.json.
5
- 3. Confirm the destination path is raw/articles and the behavior is Create.
6
- 4. Repeat the import for every browser profile that should capture into this vault.
3
+ Dedicated clip-outbox (v1):
4
+ 1. Create or select an Obsidian vault at ~/Obsidian/clip-outbox (outside ~/wiki).
5
+ 2. Create raw/articles and raw/assets in that vault. Set the attachment folder to raw/assets.
6
+ 3. Open Obsidian Web Clipper Settings in the browser profile that will capture.
7
+ 4. Import llm-wiki-clippings.json.
8
+ 5. Select the clip-outbox vault. Keep Add to Obsidian. Do not select ~/wiki.
9
+ 6. Confirm the destination path is raw/articles and the behavior is Create.
10
+ 7. Repeat the import for every browser profile. Shipping a new JSON does not update extension storage.
11
+ 8. Verify with a scratch capture and a repeated same-title save before enabling delivery.
7
12
 
8
- The generic template preserves Web Clipper's {{content}} output. Remote HTTP(S)
9
- images remain external dependencies and are not guaranteed to be downloaded or
10
- available offline.
11
-
12
- For attended local asset materialization, an agent may choose any URL-friendly
13
- path under raw/assets/. Write the asset first, use an explicit vault-qualified
14
- Obsidian embed such as ![[raw/assets/example/diagram.png]], verify the target and
15
- preview, and only then finalize the new raw capture. Referenced asset paths are
16
- stable after capture.
13
+ The generic template preserves Web Clipper's {{content}} output. The clip-outbox
14
+ worker localizes remote HTTPS images, writes explicit ![[raw/assets/...]] embeds,
15
+ and uploads a sibling Markdown index at raw/assets/<capture-id>/note.md. Never use
16
+ .txt as the only index. wiki_capture is not clip or image ingress.