@justestif/pk 0.4.0 → 0.5.0

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 +102 -94
  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,105 @@ 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 type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
36110
+
36111
+ export default function (pi: ExtensionAPI) {
36112
+ pi.on('before_agent_start', async (event) => {
36113
+ const proc = Bun.spawnSync(['pk', 'prime'], {
36114
+ env: { ...process.env, PK_KNOWLEDGE_DIR: '${knowledgeDir}' },
36115
+ });
36116
+ const output = proc.stdout?.toString().trim() ?? '';
36117
+ return output
36118
+ ? { systemPrompt: output + '\\n\\n' + event.systemPrompt }
36119
+ : undefined;
36120
+ });
36121
+
36122
+ pi.on('tool_call', async (event) => {
36123
+ if (event.tool === 'bash' && event.input?.command) {
36124
+ event.input.command = 'export PK_KNOWLEDGE_DIR=${knowledgeDir}\\n' + event.input.command;
36125
+ }
36126
+ });
36127
+ }
36132
36128
  `;
36133
36129
  }
36134
- async function writeOpenCodePlugin(projectRoot, knowledgeDir) {
36135
- const pluginPath = path11.join(projectRoot, ".opencode", "plugins", "pk-eval.ts");
36130
+ async function writePiPlugin(projectRoot, knowledgeDir) {
36131
+ const pluginPath = path11.join(projectRoot, ".pi", "extensions", "pk-eval.ts");
36136
36132
  mkdirSync6(path11.dirname(pluginPath), { recursive: true });
36137
- await Bun.write(pluginPath, openCodePluginScript(knowledgeDir));
36133
+ await Bun.write(pluginPath, piPluginScript(knowledgeDir));
36138
36134
  }
36139
36135
 
36140
36136
  // src/lib/project.ts
36141
36137
  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" }
36138
+ { hint: "SessionStart env + UserPromptSubmit context", label: "Claude Code", value: "claude" },
36139
+ { hint: "shell.env + chat.system.transform plugin", label: "OpenCode", value: "opencode" },
36140
+ { hint: "tool_call env + before_agent_start context", label: "Pi", value: "pi" }
36145
36141
  ];
36146
36142
  var HARNESS_VALUES = new Set(HARNESSES.map((h2) => h2.value));
36147
36143
  var HARNESS_ACTIVATION = {
36148
36144
  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"
36145
+ opencode: "reload OpenCode or restart the app",
36146
+ pi: "start a new Pi session in this project"
36151
36147
  };
36152
36148
  async function ensureProject(name21) {
36153
36149
  const kDir = projectDir(name21);
@@ -36208,12 +36204,10 @@ function skillTargetDir(harness, projectRoot) {
36208
36204
  case "claude": {
36209
36205
  return path12.join(os3.homedir(), ".claude", "skills", "pk");
36210
36206
  }
36211
- case "opencode": {
36207
+ case "opencode":
36208
+ case "pi": {
36212
36209
  return path12.join(projectRoot, ".agents", "skills", "pk");
36213
36210
  }
36214
- case "codex": {
36215
- return path12.join(os3.homedir(), ".codex", "skills", "pk");
36216
- }
36217
36211
  }
36218
36212
  }
36219
36213
  function skillSourceDir() {
@@ -36241,14 +36235,14 @@ async function applyHarness(harness, ctx) {
36241
36235
  await writeClaudeHook(projectRoot, knowledgeDir);
36242
36236
  break;
36243
36237
  }
36244
- case "codex": {
36245
- await writeAgentsMd(projectRoot, knowledgeDir);
36246
- break;
36247
- }
36248
36238
  case "opencode": {
36249
36239
  await writeOpenCodePlugin(projectRoot, knowledgeDir);
36250
36240
  break;
36251
36241
  }
36242
+ case "pi": {
36243
+ await writePiPlugin(projectRoot, knowledgeDir);
36244
+ break;
36245
+ }
36252
36246
  }
36253
36247
  }
36254
36248
  async function applyHarnesses(harnesses, ctx) {
@@ -36408,7 +36402,7 @@ Validates frontmatter, required sections, file location, ID uniqueness, tag form
36408
36402
  init: `pk init [<name>] [--harness <harness>]
36409
36403
  Interactive when args omitted. Creates ~/.pk/<name>/ knowledge base, configures hooks, installs skill.
36410
36404
  Detects existing projects and connects without recreating.
36411
- Harnesses: claude, codex, opencode.`,
36405
+ Harnesses: claude, opencode, pi.`,
36412
36406
  config: `pk config [--embedding model] [--no-embedding]
36413
36407
  Shows or updates pk configuration. Embedding config shape: {embedding:{enabled,provider,model}}.`
36414
36408
  };
@@ -36442,6 +36436,20 @@ function registerVocab(program2) {
36442
36436
  }));
36443
36437
  }
36444
36438
 
36439
+ // src/lib/instruction.ts
36440
+ function pkInstruction(knowledgeDir) {
36441
+ return `## pk \u2014 project knowledge
36442
+
36443
+ 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.
36444
+
36445
+ Knowledge directory: \`${knowledgeDir}\`
36446
+
36447
+ \`\`\`bash
36448
+ export PK_KNOWLEDGE_DIR="${knowledgeDir}"
36449
+ \`\`\``;
36450
+ }
36451
+ 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.";
36452
+
36445
36453
  // src/commands/prime.ts
36446
36454
  init_git();
36447
36455
  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.0",
5
5
  "description": "Project knowledge — structured intake, search, and recall",
6
6
  "bin": {
7
7
  "pk": "dist/index.js"