@justestif/pk 0.4.0 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +8 -8
  2. package/dist/index.js +105 -95
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,22 +42,22 @@ Non-interactive:
42
42
 
43
43
  ```bash
44
44
  pk init my-project --harness claude
45
- pk init my-project --harness claude,codex # multiple harnesses
45
+ pk init my-project --harness claude,opencode # multiple harnesses
46
46
  ```
47
47
 
48
- Available harnesses: `claude` (Claude Code), `codex` (Codex), `opencode` (OpenCode).
48
+ Available harnesses: `claude` (Claude Code), `opencode` (OpenCode), `pi` (Pi).
49
49
 
50
50
  `pk init` does three things:
51
51
 
52
52
  1. Creates `~/.pk/<name>/` as the knowledge home for this project
53
- 2. Installs a hook or plugin that calls `pk prime` at session start to inject context into your agent
53
+ 2. Installs a hook or plugin that injects `PK_KNOWLEDGE_DIR` into the shell and calls `pk prime` for context
54
54
  3. Installs the pk skill so your agent knows how to use the CLI
55
55
 
56
- | Harness | Files written | Mechanism |
57
- | ---------- | --------------------------------------------------- | ----------------------------------------- |
58
- | `claude` | `.claude/hooks/pk-eval.ts`, `.claude/settings.json` | Hook spawns `pk prime` on every prompt |
59
- | `codex` | `AGENTS.md` | Codex reads AGENTS.md natively |
60
- | `opencode` | `.opencode/plugins/pk-eval.ts` | Plugin spawns `pk prime` at session start |
56
+ | Harness | Files written | Env injection |
57
+ | ---------- | -------------------------------------------------------------------------- | ------------------------------------ |
58
+ | `claude` | `.claude/hooks/pk-session-start.sh`, `pk-eval.ts`, `.claude/settings.json`| `SessionStart` `$CLAUDE_ENV_FILE` |
59
+ | `opencode` | `.opencode/plugins/pk-eval.ts` | `shell.env` plugin hook |
60
+ | `pi` | `.pi/extensions/pk-eval.ts` | `tool_call` mutation |
61
61
 
62
62
  ## Commands
63
63
 
package/dist/index.js CHANGED
@@ -36019,12 +36019,19 @@ async function writeJson2(filePath, data) {
36019
36019
  await Bun.write(filePath, JSON.stringify(data, null, 2) + `
36020
36020
  `);
36021
36021
  }
36022
- function claudeHookScript(knowledgeDir) {
36022
+ function sessionStartScript(knowledgeDir) {
36023
+ return `#!/bin/bash
36024
+ # pk hook \u2014 auto-generated by pk init
36025
+ if [ -n "$CLAUDE_ENV_FILE" ]; then
36026
+ echo 'export PK_KNOWLEDGE_DIR=${knowledgeDir}' >> "$CLAUDE_ENV_FILE"
36027
+ fi
36028
+ exit 0
36029
+ `;
36030
+ }
36031
+ function userPromptSubmitScript() {
36023
36032
  return `// pk hook \u2014 auto-generated by pk init
36024
36033
  async function handleUserPromptSubmit() {
36025
- const proc = Bun.spawnSync(['pk', 'prime'], {
36026
- env: { ...process.env, PK_KNOWLEDGE_DIR: '${knowledgeDir}' }
36027
- });
36034
+ const proc = Bun.spawnSync(['pk', 'prime']);
36028
36035
  const output = proc.stdout?.toString().trim() ?? '';
36029
36036
  console.log(JSON.stringify({
36030
36037
  hookSpecificOutput: {
@@ -36038,116 +36045,107 @@ async function handleUserPromptSubmit() {
36038
36045
  handleUserPromptSubmit().catch(() => process.exit(0));
36039
36046
  `;
36040
36047
  }
36048
+ function addHookEntry(hooks, event, command) {
36049
+ const entries = hooks[event] ?? [];
36050
+ const wrappedHook = { matcher: "", hooks: [{ type: "command", command }] };
36051
+ const alreadyPresent = (entry) => Array.isArray(entry.hooks) && entry.hooks.some((h2) => typeof h2 === "object" && h2 !== null && h2.command === command);
36052
+ if (!entries.some(alreadyPresent)) {
36053
+ entries.push(wrappedHook);
36054
+ }
36055
+ hooks[event] = entries;
36056
+ }
36041
36057
  async function writeClaudeHook(projectRoot, knowledgeDir) {
36042
36058
  const hookDir = path9.join(projectRoot, ".claude", "hooks");
36043
- const hookPath = path9.join(hookDir, "pk-eval.ts");
36044
36059
  mkdirSync4(hookDir, { recursive: true });
36045
- await Bun.write(hookPath, claudeHookScript(knowledgeDir));
36060
+ const sessionStartPath = path9.join(hookDir, "pk-session-start.sh");
36061
+ await Bun.write(sessionStartPath, sessionStartScript(knowledgeDir));
36062
+ const { chmodSync } = await import("fs");
36063
+ chmodSync(sessionStartPath, 493);
36064
+ const evalPath = path9.join(hookDir, "pk-eval.ts");
36065
+ await Bun.write(evalPath, userPromptSubmitScript());
36046
36066
  const settingsPath = path9.join(projectRoot, ".claude", "settings.json");
36047
36067
  const settings = await readJson(settingsPath);
36048
36068
  const hooks = settings.hooks ?? {};
36049
- const ups = hooks.UserPromptSubmit ?? [];
36050
- const hookCmd = `bun run ${hookPath}`;
36051
- const wrappedHook = {
36052
- matcher: "",
36053
- hooks: [
36054
- {
36055
- type: "command",
36056
- command: hookCmd
36057
- }
36058
- ]
36059
- };
36060
- const hasPkEval = (entry) => typeof entry === "object" && entry !== null && ("hooks" in entry) && Array.isArray(entry.hooks) && entry.hooks.some((hook) => typeof hook === "object" && hook !== null && ("type" in hook) && hook.type === "command" && ("command" in hook) && hook.command === hookCmd);
36061
- if (!ups.some(hasPkEval)) {
36062
- ups.push(wrappedHook);
36063
- }
36064
- hooks.UserPromptSubmit = ups;
36069
+ addHookEntry(hooks, "SessionStart", sessionStartPath);
36070
+ addHookEntry(hooks, "UserPromptSubmit", `bun run ${evalPath}`);
36065
36071
  settings.hooks = hooks;
36066
36072
  await writeJson2(settingsPath, settings);
36067
36073
  }
36068
36074
 
36069
- // src/commands/harnesses/codex.ts
36075
+ // src/commands/harnesses/opencode.ts
36070
36076
  import { mkdirSync as mkdirSync5 } from "fs";
36071
36077
  import path10 from "path";
36072
-
36073
- // src/lib/instruction.ts
36074
- function pkInstruction(knowledgeDir) {
36075
- return `## pk \u2014 project knowledge
36076
-
36077
- Use the pk skill and its CLI commands to manage project knowledge for this project. Run \`pk synthesize --session-start\` at the start of every session.
36078
-
36079
- Knowledge directory: \`${knowledgeDir}\`
36080
-
36081
- \`\`\`bash
36082
- export PK_KNOWLEDGE_DIR="${knowledgeDir}"
36083
- \`\`\``;
36084
- }
36085
- var FORCED_EVAL_PROMPT = "SKILL EVALUATION REQUIRED: Before responding, explicitly decide YES or NO" + " \u2014 does this request involve project work, investigation, decisions, or knowledge capture" + " where the pk skill applies? If YES, activate the pk skill, then proceed.";
36086
-
36087
- // src/commands/harnesses/codex.ts
36088
- var PK_SECTION_START = "<!-- pk:start -->";
36089
- var PK_SECTION_END = "<!-- pk:end -->";
36090
- async function writeInstructionSection(filePath, content) {
36091
- const section = `${PK_SECTION_START}
36092
- ${content}
36093
- ${PK_SECTION_END}
36078
+ function openCodePluginScript(knowledgeDir) {
36079
+ return `// pk plugin \u2014 auto-generated by pk init
36080
+ export const pkPlugin = async (_input: unknown) => ({
36081
+ 'shell.env': async (
36082
+ _input: unknown,
36083
+ output: { env: Record<string, string> },
36084
+ ): Promise<void> => {
36085
+ output.env.PK_KNOWLEDGE_DIR = '${knowledgeDir}';
36086
+ },
36087
+ 'experimental.chat.system.transform': async (
36088
+ _input: unknown,
36089
+ output: { system: string[] },
36090
+ ): Promise<void> => {
36091
+ const proc = Bun.spawnSync(['pk', 'prime']);
36092
+ const text = proc.stdout?.toString().trim() ?? '';
36093
+ if (text) output.system.unshift(text);
36094
+ },
36095
+ });
36094
36096
  `;
36095
- let existing = "";
36096
- try {
36097
- existing = await Bun.file(filePath).text();
36098
- } catch {}
36099
- const startIdx = existing.indexOf(PK_SECTION_START);
36100
- const endIdx = existing.indexOf(PK_SECTION_END);
36101
- let updated;
36102
- if (startIdx !== -1 && endIdx !== -1) {
36103
- updated = existing.slice(0, startIdx) + section + existing.slice(endIdx + PK_SECTION_END.length + 1);
36104
- } else {
36105
- updated = existing ? existing.trimEnd() + `
36106
-
36107
- ` + section : section;
36108
- }
36109
- mkdirSync5(path10.dirname(filePath), { recursive: true });
36110
- await Bun.write(filePath, updated);
36111
36097
  }
36112
- async function writeAgentsMd(projectRoot, knowledgeDir) {
36113
- await writeInstructionSection(path10.join(projectRoot, "AGENTS.md"), pkInstruction(knowledgeDir));
36098
+ async function writeOpenCodePlugin(projectRoot, knowledgeDir) {
36099
+ const pluginPath = path10.join(projectRoot, ".opencode", "plugins", "pk-eval.ts");
36100
+ mkdirSync5(path10.dirname(pluginPath), { recursive: true });
36101
+ await Bun.write(pluginPath, openCodePluginScript(knowledgeDir));
36114
36102
  }
36115
36103
 
36116
- // src/commands/harnesses/opencode.ts
36104
+ // src/commands/harnesses/pi.ts
36117
36105
  import { mkdirSync as mkdirSync6 } from "fs";
36118
36106
  import path11 from "path";
36119
- function openCodePluginScript(knowledgeDir) {
36120
- return `// pk plugin \u2014 auto-generated by pk init
36121
- export const experimental = {
36122
- async 'chat.system.transform'({ system }: { system: string[] }): Promise<void> {
36123
- const proc = Bun.spawnSync(['pk', 'prime'], {
36124
- env: { ...process.env, PK_KNOWLEDGE_DIR: '${knowledgeDir}' }
36125
- });
36126
- const output = proc.stdout?.toString().trim() ?? '';
36127
- if (output) {
36128
- system.unshift(output);
36129
- }
36130
- },
36131
- };
36107
+ function piPluginScript(knowledgeDir) {
36108
+ return `// pk extension \u2014 auto-generated by pk init
36109
+ import { spawnSync } from 'node:child_process';
36110
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
36111
+
36112
+ export default function (pi: ExtensionAPI) {
36113
+ pi.on('before_agent_start', async (event, _ctx) => {
36114
+ const proc = spawnSync('pk', ['prime'], {
36115
+ env: { ...process.env, PK_KNOWLEDGE_DIR: '${knowledgeDir}' },
36116
+ encoding: 'utf-8',
36117
+ });
36118
+ const output = proc.stdout?.trim() ?? '';
36119
+ return output
36120
+ ? { systemPrompt: output + '\\n\\n' + event.systemPrompt }
36121
+ : undefined;
36122
+ });
36123
+
36124
+ pi.on('tool_call', async (event, _ctx) => {
36125
+ if (event.toolName === 'bash' && event.input?.command) {
36126
+ event.input.command = 'export PK_KNOWLEDGE_DIR=${knowledgeDir}\\n' + event.input.command;
36127
+ }
36128
+ });
36129
+ }
36132
36130
  `;
36133
36131
  }
36134
- async function writeOpenCodePlugin(projectRoot, knowledgeDir) {
36135
- const pluginPath = path11.join(projectRoot, ".opencode", "plugins", "pk-eval.ts");
36132
+ async function writePiPlugin(projectRoot, knowledgeDir) {
36133
+ const pluginPath = path11.join(projectRoot, ".pi", "extensions", "pk-eval.ts");
36136
36134
  mkdirSync6(path11.dirname(pluginPath), { recursive: true });
36137
- await Bun.write(pluginPath, openCodePluginScript(knowledgeDir));
36135
+ await Bun.write(pluginPath, piPluginScript(knowledgeDir));
36138
36136
  }
36139
36137
 
36140
36138
  // src/lib/project.ts
36141
36139
  var HARNESSES = [
36142
- { hint: "forced-eval hook", label: "Claude Code", value: "claude" },
36143
- { hint: "AGENTS.md injection", label: "Codex", value: "codex" },
36144
- { hint: "forced-eval plugin", label: "OpenCode", value: "opencode" }
36140
+ { hint: "SessionStart env + UserPromptSubmit context", label: "Claude Code", value: "claude" },
36141
+ { hint: "shell.env + chat.system.transform plugin", label: "OpenCode", value: "opencode" },
36142
+ { hint: "tool_call env + before_agent_start context", label: "Pi", value: "pi" }
36145
36143
  ];
36146
36144
  var HARNESS_VALUES = new Set(HARNESSES.map((h2) => h2.value));
36147
36145
  var HARNESS_ACTIVATION = {
36148
36146
  claude: "start a new Claude Code session in this project",
36149
- codex: "start a new Codex session in this project",
36150
- opencode: "reload OpenCode or restart the app"
36147
+ opencode: "reload OpenCode or restart the app",
36148
+ pi: "start a new Pi session in this project"
36151
36149
  };
36152
36150
  async function ensureProject(name21) {
36153
36151
  const kDir = projectDir(name21);
@@ -36206,14 +36204,12 @@ async function initializeProject(options2) {
36206
36204
  function skillTargetDir(harness, projectRoot) {
36207
36205
  switch (harness) {
36208
36206
  case "claude": {
36209
- return path12.join(os3.homedir(), ".claude", "skills", "pk");
36207
+ return path12.join(projectRoot, ".claude", "skills", "pk");
36210
36208
  }
36211
- case "opencode": {
36209
+ case "opencode":
36210
+ case "pi": {
36212
36211
  return path12.join(projectRoot, ".agents", "skills", "pk");
36213
36212
  }
36214
- case "codex": {
36215
- return path12.join(os3.homedir(), ".codex", "skills", "pk");
36216
- }
36217
36213
  }
36218
36214
  }
36219
36215
  function skillSourceDir() {
@@ -36241,14 +36237,14 @@ async function applyHarness(harness, ctx) {
36241
36237
  await writeClaudeHook(projectRoot, knowledgeDir);
36242
36238
  break;
36243
36239
  }
36244
- case "codex": {
36245
- await writeAgentsMd(projectRoot, knowledgeDir);
36246
- break;
36247
- }
36248
36240
  case "opencode": {
36249
36241
  await writeOpenCodePlugin(projectRoot, knowledgeDir);
36250
36242
  break;
36251
36243
  }
36244
+ case "pi": {
36245
+ await writePiPlugin(projectRoot, knowledgeDir);
36246
+ break;
36247
+ }
36252
36248
  }
36253
36249
  }
36254
36250
  async function applyHarnesses(harnesses, ctx) {
@@ -36408,7 +36404,7 @@ Validates frontmatter, required sections, file location, ID uniqueness, tag form
36408
36404
  init: `pk init [<name>] [--harness <harness>]
36409
36405
  Interactive when args omitted. Creates ~/.pk/<name>/ knowledge base, configures hooks, installs skill.
36410
36406
  Detects existing projects and connects without recreating.
36411
- Harnesses: claude, codex, opencode.`,
36407
+ Harnesses: claude, opencode, pi.`,
36412
36408
  config: `pk config [--embedding model] [--no-embedding]
36413
36409
  Shows or updates pk configuration. Embedding config shape: {embedding:{enabled,provider,model}}.`
36414
36410
  };
@@ -36442,6 +36438,20 @@ function registerVocab(program2) {
36442
36438
  }));
36443
36439
  }
36444
36440
 
36441
+ // src/lib/instruction.ts
36442
+ function pkInstruction(knowledgeDir) {
36443
+ return `## pk \u2014 project knowledge
36444
+
36445
+ Use the pk skill and its CLI commands to manage project knowledge for this project. Run \`pk synthesize --session-start\` at the start of every session.
36446
+
36447
+ Knowledge directory: \`${knowledgeDir}\`
36448
+
36449
+ \`\`\`bash
36450
+ export PK_KNOWLEDGE_DIR="${knowledgeDir}"
36451
+ \`\`\``;
36452
+ }
36453
+ var FORCED_EVAL_PROMPT = "SKILL EVALUATION REQUIRED: Before responding, explicitly decide YES or NO" + " \u2014 does this request involve project work, investigation, decisions, or knowledge capture" + " where the pk skill applies? If YES, activate the pk skill, then proceed.";
36454
+
36445
36455
  // src/commands/prime.ts
36446
36456
  init_git();
36447
36457
  function registerPrime(program2) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justestif/pk",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.5.1",
5
5
  "description": "Project knowledge — structured intake, search, and recall",
6
6
  "bin": {
7
7
  "pk": "dist/index.js"