@tekmidian/pai 0.5.6 → 0.5.7
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/README.md +20 -2
- package/dist/cli/index.mjs +479 -5
- package/dist/cli/index.mjs.map +1 -1
- package/dist/daemon/index.mjs +2 -2
- package/dist/{daemon-D9evGlgR.mjs → daemon-2ND5WO2j.mjs} +3 -3
- package/dist/{daemon-D9evGlgR.mjs.map → daemon-2ND5WO2j.mjs.map} +1 -1
- package/dist/{db-4lSqLFb8.mjs → db-BtuN768f.mjs} +9 -2
- package/dist/db-BtuN768f.mjs.map +1 -0
- package/dist/hooks/capture-all-events.mjs +19 -4
- package/dist/hooks/capture-all-events.mjs.map +4 -4
- package/dist/hooks/cleanup-session-files.mjs.map +2 -2
- package/dist/hooks/context-compression-hook.mjs +14 -9
- package/dist/hooks/context-compression-hook.mjs.map +3 -3
- package/dist/hooks/initialize-session.mjs +14 -8
- package/dist/hooks/initialize-session.mjs.map +3 -3
- package/dist/hooks/load-core-context.mjs +18 -2
- package/dist/hooks/load-core-context.mjs.map +4 -4
- package/dist/hooks/load-project-context.mjs +14 -8
- package/dist/hooks/load-project-context.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +105 -8
- package/dist/hooks/stop-hook.mjs.map +3 -3
- package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.mjs +1 -1
- package/dist/{vault-indexer-DXWs9pDn.mjs → vault-indexer-k-kUlaZ-.mjs} +41 -7
- package/dist/vault-indexer-k-kUlaZ-.mjs.map +1 -0
- package/package.json +1 -1
- package/src/hooks/ts/capture-all-events.ts +6 -0
- package/src/hooks/ts/lib/project-utils.ts +24 -5
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +6 -0
- package/src/hooks/ts/session-start/initialize-session.ts +7 -1
- package/src/hooks/ts/session-start/load-core-context.ts +7 -0
- package/src/hooks/ts/session-start/load-project-context.ts +8 -1
- package/src/hooks/ts/stop/stop-hook.ts +28 -0
- package/templates/claude-md.template.md +7 -74
- package/templates/skills/CORE/Aesthetic.md +333 -0
- package/templates/skills/CORE/CONSTITUTION.md +1502 -0
- package/templates/skills/CORE/HistorySystem.md +427 -0
- package/templates/skills/CORE/HookSystem.md +1082 -0
- package/templates/skills/CORE/Prompting.md +509 -0
- package/templates/skills/CORE/ProsodyAgentTemplate.md +53 -0
- package/templates/skills/CORE/ProsodyGuide.md +416 -0
- package/templates/skills/CORE/SKILL.md +741 -0
- package/templates/skills/CORE/SkillSystem.md +213 -0
- package/templates/skills/CORE/TerminalTabs.md +119 -0
- package/templates/skills/CORE/VOICE.md +106 -0
- package/templates/skills/user/.gitkeep +0 -0
- package/dist/db-4lSqLFb8.mjs.map +0 -1
- package/dist/vault-indexer-DXWs9pDn.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -29,6 +29,13 @@ Install PAI and Claude remembers. Ask it what you were working on. Ask it to fin
|
|
|
29
29
|
- "What were we working on last week?" — Claude knows, without you re-explaining
|
|
30
30
|
- "Clean up my session notes" — auto-names unnamed sessions and organizes by date
|
|
31
31
|
|
|
32
|
+
### Reviewing Your Work
|
|
33
|
+
|
|
34
|
+
- "Review my week" — synthesizes session notes, git commits, and completed tasks into a themed narrative
|
|
35
|
+
- "What did I do today?" — daily review across all projects
|
|
36
|
+
- "Journal this thought" — capture freeform reflections with timestamps
|
|
37
|
+
- "Plan my week" — forward-looking priorities based on open TODOs and recent activity
|
|
38
|
+
|
|
32
39
|
### Keeping Things Safe
|
|
33
40
|
|
|
34
41
|
- "Back up everything" — creates a timestamped backup of all your data
|
|
@@ -297,7 +304,7 @@ PAI implements Niklas Luhmann's Zettelkasten principles as six computational ope
|
|
|
297
304
|
|
|
298
305
|
### How it works
|
|
299
306
|
|
|
300
|
-
PAI indexes your entire vault — following symlinks, deduplicating by inode, parsing every
|
|
307
|
+
PAI indexes your entire vault — following symlinks, deduplicating by inode, parsing every link — and builds a graph database alongside semantic embeddings. Six tools then operate on this dual representation:
|
|
301
308
|
|
|
302
309
|
| Tool | What it does |
|
|
303
310
|
|------|-------------|
|
|
@@ -312,7 +319,18 @@ All tools work as CLI commands (`pai zettel <command>`) and MCP tools (`zettel_*
|
|
|
312
319
|
|
|
313
320
|
### Vault Indexing
|
|
314
321
|
|
|
315
|
-
The vault indexer follows symlinks (critical for vaults built on symlinks), deduplicates files by inode to handle multiple paths to the same file, and builds a complete
|
|
322
|
+
The vault indexer follows symlinks (critical for vaults built on symlinks), deduplicates files by inode to handle multiple paths to the same file, and builds a complete link graph with Obsidian-compatible shortest-match resolution.
|
|
323
|
+
|
|
324
|
+
All link types are parsed and resolved:
|
|
325
|
+
|
|
326
|
+
| Syntax | Type | Example |
|
|
327
|
+
|--------|------|---------|
|
|
328
|
+
| `[[Note]]` | Wikilink | `[[Daily Note]]`, `[[Note\|alias]]`, `[[Note#heading]]` |
|
|
329
|
+
| `![[file]]` | Embed | `![[diagram.png]]`, `![[template]]` |
|
|
330
|
+
| `[text](path.md)` | Markdown link | `[see here](notes/idea.md)`, `[ref](note.md#section)` |
|
|
331
|
+
| `` | Markdown embed | `` |
|
|
332
|
+
|
|
333
|
+
External URLs (`https://`, `mailto:`, etc.) are excluded — only relative paths are treated as vault connections. URL-encoded paths (e.g. `my%20note.md`) are decoded automatically.
|
|
316
334
|
|
|
317
335
|
- Full index: ~10 seconds for ~1,000 files
|
|
318
336
|
- Incremental: ~2 seconds (hash-based change detection)
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as openRegistry } from "../db-
|
|
2
|
+
import { n as openRegistry } from "../db-BtuN768f.mjs";
|
|
3
3
|
import { _ as warn, a as fmtDate, c as ok, d as scaffoldProjectDirs, f as shortenPath, i as err, l as renderTable, m as slugify, n as dim, o as header, p as slugFromPath, r as encodeDir, s as now, t as bold, u as resolvePath } from "../utils-QSfKagcj.mjs";
|
|
4
4
|
import { a as slugify$1, i as parseSessionFilename, n as decodeEncodedDir, t as buildEncodedDirMap } from "../migrate-jokLenje.mjs";
|
|
5
5
|
import { n as ensurePaiMarker, t as discoverPaiMarkers } from "../pai-marker-CXQPX2P6.mjs";
|
|
@@ -187,6 +187,16 @@ function cmdAdd(db, rawPath, opts) {
|
|
|
187
187
|
console.error(err(`Project already registered (slug: ${slug} or path: ${rootPath})`));
|
|
188
188
|
process.exit(1);
|
|
189
189
|
}
|
|
190
|
+
const dirName = basename(rootPath).toLowerCase();
|
|
191
|
+
const matches = db.prepare(`SELECT slug, root_path FROM projects
|
|
192
|
+
WHERE status = 'active' AND slug != ?`).all(slug).filter((s) => basename(s.root_path).toLowerCase() === dirName || s.slug.replace(/-\d+$/, "") === slug.replace(/-\d+$/, ""));
|
|
193
|
+
if (matches.length > 0) {
|
|
194
|
+
console.log(warn(`Similar project(s) already registered:`));
|
|
195
|
+
for (const m of matches) console.log(dim(` ${bold(m.slug)} ${shortenPath(m.root_path, 50)}`));
|
|
196
|
+
console.log(dim(` Consider: pai project alias ${matches[0].slug} <name> (to link them)`));
|
|
197
|
+
console.log(dim(` Or: pai project archive ${slug} (if this is a duplicate)`));
|
|
198
|
+
console.log();
|
|
199
|
+
}
|
|
190
200
|
const ts = now();
|
|
191
201
|
db.prepare(`INSERT INTO projects
|
|
192
202
|
(slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)
|
|
@@ -352,6 +362,406 @@ function cmdAlias(db, slug, alias) {
|
|
|
352
362
|
process.exit(1);
|
|
353
363
|
}
|
|
354
364
|
}
|
|
365
|
+
const CONFIG_OPTIONS = [
|
|
366
|
+
{
|
|
367
|
+
key: "permission",
|
|
368
|
+
type: "string",
|
|
369
|
+
description: "Permission preset: full | trusted | default",
|
|
370
|
+
examples: [
|
|
371
|
+
"full",
|
|
372
|
+
"trusted",
|
|
373
|
+
"default"
|
|
374
|
+
]
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
key: "flags",
|
|
378
|
+
type: "string",
|
|
379
|
+
description: "Raw Claude CLI flags",
|
|
380
|
+
examples: ["--dangerously-skip-permissions", "--allowedTools Edit,Read"]
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
key: "env",
|
|
384
|
+
type: "object",
|
|
385
|
+
description: "Environment variables as key=value pairs",
|
|
386
|
+
examples: ["IS_SANDBOX=1", "CLAUDE_MODEL=opus"]
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
key: "autoStart",
|
|
390
|
+
type: "boolean",
|
|
391
|
+
description: "Auto-start session (skip interactive prompt)",
|
|
392
|
+
examples: ["true", "false"]
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
key: "prompt",
|
|
396
|
+
type: "string",
|
|
397
|
+
description: "Initial prompt sent to Claude on launch",
|
|
398
|
+
examples: [
|
|
399
|
+
"go",
|
|
400
|
+
"continue",
|
|
401
|
+
"run tests"
|
|
402
|
+
]
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
key: "model",
|
|
406
|
+
type: "string",
|
|
407
|
+
description: "Model override for the session",
|
|
408
|
+
examples: [
|
|
409
|
+
"opus",
|
|
410
|
+
"sonnet",
|
|
411
|
+
"haiku"
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
];
|
|
415
|
+
const PERMISSION_PRESETS = {
|
|
416
|
+
full: {
|
|
417
|
+
permission: "full",
|
|
418
|
+
flags: "--dangerously-skip-permissions",
|
|
419
|
+
env: { IS_SANDBOX: "1" },
|
|
420
|
+
autoStart: true,
|
|
421
|
+
prompt: "go"
|
|
422
|
+
},
|
|
423
|
+
trusted: {
|
|
424
|
+
permission: "trusted",
|
|
425
|
+
flags: "",
|
|
426
|
+
env: {},
|
|
427
|
+
autoStart: true,
|
|
428
|
+
prompt: "go"
|
|
429
|
+
},
|
|
430
|
+
default: {
|
|
431
|
+
permission: "default",
|
|
432
|
+
flags: "",
|
|
433
|
+
env: {},
|
|
434
|
+
autoStart: false
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
function getSessionConfig(project) {
|
|
438
|
+
return project.session_config ? JSON.parse(project.session_config) : {};
|
|
439
|
+
}
|
|
440
|
+
function getGlobalDefaults() {
|
|
441
|
+
try {
|
|
442
|
+
const configPath = join(homedir(), ".config", "pai", "config.json");
|
|
443
|
+
if (existsSync(configPath)) return JSON.parse(readFileSync(configPath, "utf-8")).sessionDefaults ?? {};
|
|
444
|
+
} catch {}
|
|
445
|
+
return {};
|
|
446
|
+
}
|
|
447
|
+
function saveGlobalDefaults(defaults) {
|
|
448
|
+
const configPath = join(homedir(), ".config", "pai", "config.json");
|
|
449
|
+
let config = {};
|
|
450
|
+
try {
|
|
451
|
+
if (existsSync(configPath)) config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
452
|
+
} catch {}
|
|
453
|
+
config.sessionDefaults = defaults;
|
|
454
|
+
mkdirSync(join(homedir(), ".config", "pai"), { recursive: true });
|
|
455
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
456
|
+
}
|
|
457
|
+
function applyPreset(config, preset) {
|
|
458
|
+
const presetConfig = PERMISSION_PRESETS[preset];
|
|
459
|
+
if (!presetConfig) return {
|
|
460
|
+
...config,
|
|
461
|
+
permission: "custom",
|
|
462
|
+
flags: preset
|
|
463
|
+
};
|
|
464
|
+
return {
|
|
465
|
+
...config,
|
|
466
|
+
...presetConfig
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function parseConfigValue(key, value) {
|
|
470
|
+
const option = CONFIG_OPTIONS.find((o) => o.key === key);
|
|
471
|
+
if (!option) return value;
|
|
472
|
+
switch (option.type) {
|
|
473
|
+
case "boolean": return value === "true" || value === "1" || value === "yes";
|
|
474
|
+
case "object":
|
|
475
|
+
if (key === "env") {
|
|
476
|
+
const [k, ...vParts] = value.split("=");
|
|
477
|
+
return { [k]: vParts.join("=") || "1" };
|
|
478
|
+
}
|
|
479
|
+
return value;
|
|
480
|
+
default: return value;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function cmdName(db, identifier, shortname, opts) {
|
|
484
|
+
const project = resolveIdentifier(db, identifier) ?? requireProject(db, identifier);
|
|
485
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(shortname)) {
|
|
486
|
+
console.error(err(`Invalid name "${shortname}". Use letters, digits, hyphens, underscores. Must start with a letter.`));
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
if (db.prepare("SELECT id FROM projects WHERE slug = ? AND id != ?").get(shortname, project.id)) {
|
|
490
|
+
console.error(err(`"${shortname}" is already a project slug.`));
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
const conflictAlias = db.prepare("SELECT project_id FROM aliases WHERE alias = ?").get(shortname);
|
|
494
|
+
if (conflictAlias && conflictAlias.project_id !== project.id) {
|
|
495
|
+
console.error(err(`"${shortname}" is already used by another project.`));
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
if (!conflictAlias) db.prepare("INSERT INTO aliases (alias, project_id) VALUES (?, ?)").run(shortname, project.id);
|
|
499
|
+
if (opts.permission) {
|
|
500
|
+
const config = applyPreset(getSessionConfig(project), opts.permission);
|
|
501
|
+
db.prepare("UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(config), now(), project.id);
|
|
502
|
+
}
|
|
503
|
+
console.log(ok(`Named: ${bold(shortname)} → ${project.slug} (${shortenPath(project.root_path, 50)})`));
|
|
504
|
+
if (opts.permission) console.log(dim(` Permission: ${opts.permission}`));
|
|
505
|
+
}
|
|
506
|
+
function cmdUnname(db, shortname) {
|
|
507
|
+
const alias = db.prepare("SELECT project_id FROM aliases WHERE alias = ?").get(shortname);
|
|
508
|
+
if (!alias) {
|
|
509
|
+
console.error(err(`No named project found: "${shortname}"`));
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
db.prepare("DELETE FROM aliases WHERE alias = ?").run(shortname);
|
|
513
|
+
const remaining = db.prepare("SELECT COUNT(*) AS cnt FROM aliases WHERE project_id = ?").get(alias.project_id);
|
|
514
|
+
console.log(ok(`Removed name: ${bold(shortname)}`));
|
|
515
|
+
if (remaining.cnt === 0) console.log(dim(" Project has no remaining names."));
|
|
516
|
+
}
|
|
517
|
+
function cmdNames(db, opts) {
|
|
518
|
+
const rows = db.prepare(`
|
|
519
|
+
SELECT p.*, a.alias AS name,
|
|
520
|
+
(SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,
|
|
521
|
+
(SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active
|
|
522
|
+
FROM projects p
|
|
523
|
+
JOIN aliases a ON a.project_id = p.id
|
|
524
|
+
WHERE p.status = 'active'
|
|
525
|
+
ORDER BY p.updated_at DESC
|
|
526
|
+
`).all();
|
|
527
|
+
if (opts.json) {
|
|
528
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
529
|
+
for (const row of rows) if (!grouped.has(row.id)) grouped.set(row.id, {
|
|
530
|
+
name: row.name,
|
|
531
|
+
names: [row.name],
|
|
532
|
+
slug: row.slug,
|
|
533
|
+
display_name: row.display_name,
|
|
534
|
+
root_path: row.root_path,
|
|
535
|
+
session_count: row.session_count,
|
|
536
|
+
last_active: row.last_active ? new Date(row.last_active).toISOString() : null,
|
|
537
|
+
session_config: row.session_config ? JSON.parse(row.session_config) : null
|
|
538
|
+
});
|
|
539
|
+
else grouped.get(row.id).names.push(row.name);
|
|
540
|
+
console.log(JSON.stringify([...grouped.values()], null, 2));
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (!rows.length) {
|
|
544
|
+
console.log(warn("No named projects. Use: pai project name <slug-or-number> <shortname>"));
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const seen = /* @__PURE__ */ new Set();
|
|
548
|
+
const tableRows = [];
|
|
549
|
+
for (const row of rows) {
|
|
550
|
+
if (seen.has(row.id)) continue;
|
|
551
|
+
seen.add(row.id);
|
|
552
|
+
const allNames = rows.filter((r) => r.id === row.id).map((r) => r.name);
|
|
553
|
+
const perm = (row.session_config ? JSON.parse(row.session_config) : null)?.permission ?? dim("default");
|
|
554
|
+
tableRows.push([
|
|
555
|
+
bold(allNames.join(", ")),
|
|
556
|
+
row.slug,
|
|
557
|
+
dim(shortenPath(row.root_path, 40)),
|
|
558
|
+
typeof perm === "string" ? perm : dim("default"),
|
|
559
|
+
String(row.session_count),
|
|
560
|
+
fmtDate(row.last_active)
|
|
561
|
+
]);
|
|
562
|
+
}
|
|
563
|
+
console.log();
|
|
564
|
+
console.log(header(" Named Projects"));
|
|
565
|
+
console.log();
|
|
566
|
+
console.log(renderTable([
|
|
567
|
+
"Name(s)",
|
|
568
|
+
"Slug",
|
|
569
|
+
"Path",
|
|
570
|
+
"Permission",
|
|
571
|
+
"Sessions",
|
|
572
|
+
"Last Active"
|
|
573
|
+
], tableRows));
|
|
574
|
+
console.log();
|
|
575
|
+
console.log(dim(` ${tableRows.length} named project(s)`));
|
|
576
|
+
}
|
|
577
|
+
function cmdConfig(db, identifier, opts) {
|
|
578
|
+
if (opts.options) {
|
|
579
|
+
if (opts.json) {
|
|
580
|
+
console.log(JSON.stringify({
|
|
581
|
+
options: CONFIG_OPTIONS,
|
|
582
|
+
presets: Object.entries(PERMISSION_PRESETS).map(([name, config]) => ({
|
|
583
|
+
name,
|
|
584
|
+
...config
|
|
585
|
+
}))
|
|
586
|
+
}, null, 2));
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
console.log();
|
|
590
|
+
console.log(header(" Session Config Options"));
|
|
591
|
+
console.log();
|
|
592
|
+
console.log(bold(" Available keys:"));
|
|
593
|
+
console.log();
|
|
594
|
+
for (const opt of CONFIG_OPTIONS) {
|
|
595
|
+
console.log(` ${bold(opt.key.padEnd(14))} ${dim(`(${opt.type})`)} ${opt.description}`);
|
|
596
|
+
console.log(` ${" ".repeat(14)} ${dim("e.g.")} ${opt.examples.map((e) => chalk.cyan(e)).join(", ")}`);
|
|
597
|
+
}
|
|
598
|
+
console.log();
|
|
599
|
+
console.log(bold(" Permission presets:"));
|
|
600
|
+
console.log();
|
|
601
|
+
for (const [name, config] of Object.entries(PERMISSION_PRESETS)) {
|
|
602
|
+
const parts = [];
|
|
603
|
+
if (config.flags) parts.push(`flags: ${config.flags}`);
|
|
604
|
+
if (config.env && Object.keys(config.env).length) parts.push(`env: ${Object.entries(config.env).map(([k, v]) => `${k}=${v}`).join(" ")}`);
|
|
605
|
+
if (config.autoStart) parts.push("autoStart");
|
|
606
|
+
if (config.prompt) parts.push(`prompt: "${config.prompt}"`);
|
|
607
|
+
console.log(` ${bold(name.padEnd(10))} ${dim(parts.join(", ") || "(vanilla)")}`);
|
|
608
|
+
}
|
|
609
|
+
console.log();
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (opts.defaults) {
|
|
613
|
+
let defaults = getGlobalDefaults();
|
|
614
|
+
if (opts.reset) {
|
|
615
|
+
saveGlobalDefaults({});
|
|
616
|
+
console.log(ok("Global session defaults reset."));
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (opts.preset) {
|
|
620
|
+
defaults = applyPreset(defaults, opts.preset);
|
|
621
|
+
saveGlobalDefaults(defaults);
|
|
622
|
+
console.log(ok(`Global defaults set to preset: ${bold(opts.preset)}`));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (opts.set?.length) {
|
|
626
|
+
for (const pair of opts.set) {
|
|
627
|
+
const eqIdx = pair.indexOf("=");
|
|
628
|
+
if (eqIdx === -1) {
|
|
629
|
+
console.error(err(`Invalid format: "${pair}". Use key=value.`));
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
const key = pair.substring(0, eqIdx);
|
|
633
|
+
const value = pair.substring(eqIdx + 1);
|
|
634
|
+
if (!CONFIG_OPTIONS.find((o) => o.key === key)) {
|
|
635
|
+
console.error(err(`Unknown key: "${key}". Run: pai project config --options`));
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (key === "env") defaults.env = {
|
|
639
|
+
...defaults.env ?? {},
|
|
640
|
+
...parseConfigValue("env", value)
|
|
641
|
+
};
|
|
642
|
+
else defaults[key] = parseConfigValue(key, value);
|
|
643
|
+
}
|
|
644
|
+
saveGlobalDefaults(defaults);
|
|
645
|
+
console.log(ok("Global defaults updated."));
|
|
646
|
+
}
|
|
647
|
+
if (opts.unset?.length) {
|
|
648
|
+
for (const key of opts.unset) if (key.startsWith("env.")) {
|
|
649
|
+
const envKey = key.substring(4);
|
|
650
|
+
if (defaults.env) delete defaults.env[envKey];
|
|
651
|
+
} else delete defaults[key];
|
|
652
|
+
saveGlobalDefaults(defaults);
|
|
653
|
+
console.log(ok("Global defaults updated."));
|
|
654
|
+
}
|
|
655
|
+
if (opts.json) console.log(JSON.stringify(defaults, null, 2));
|
|
656
|
+
else {
|
|
657
|
+
console.log();
|
|
658
|
+
console.log(header(" Global Session Defaults"));
|
|
659
|
+
console.log();
|
|
660
|
+
if (Object.keys(defaults).length === 0) {
|
|
661
|
+
console.log(dim(" No defaults set. New sessions use vanilla Claude."));
|
|
662
|
+
console.log(dim(" Set with: pai project config --defaults --preset full"));
|
|
663
|
+
} else for (const [key, value] of Object.entries(defaults)) {
|
|
664
|
+
const display = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
665
|
+
console.log(` ${bold(key.padEnd(14))} ${display}`);
|
|
666
|
+
}
|
|
667
|
+
console.log();
|
|
668
|
+
}
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (!identifier) {
|
|
672
|
+
console.error(err("Specify a project: pai project config <name-or-slug>"));
|
|
673
|
+
console.error(dim(" Or use --defaults for global defaults, --options for available keys."));
|
|
674
|
+
process.exit(1);
|
|
675
|
+
}
|
|
676
|
+
const project = resolveIdentifier(db, identifier) ?? requireProject(db, identifier);
|
|
677
|
+
let config = getSessionConfig(project);
|
|
678
|
+
if (opts.reset) {
|
|
679
|
+
db.prepare("UPDATE projects SET session_config = NULL, updated_at = ? WHERE id = ?").run(now(), project.id);
|
|
680
|
+
console.log(ok(`Config reset for ${bold(project.slug)}. Will use global defaults.`));
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (opts.preset) {
|
|
684
|
+
config = applyPreset(config, opts.preset);
|
|
685
|
+
db.prepare("UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(config), now(), project.id);
|
|
686
|
+
console.log(ok(`Applied preset ${bold(opts.preset)} to ${bold(project.slug)}`));
|
|
687
|
+
}
|
|
688
|
+
if (opts.set?.length) {
|
|
689
|
+
for (const pair of opts.set) {
|
|
690
|
+
const eqIdx = pair.indexOf("=");
|
|
691
|
+
if (eqIdx === -1) {
|
|
692
|
+
console.error(err(`Invalid format: "${pair}". Use key=value.`));
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
const key = pair.substring(0, eqIdx);
|
|
696
|
+
const value = pair.substring(eqIdx + 1);
|
|
697
|
+
if (!CONFIG_OPTIONS.find((o) => o.key === key)) {
|
|
698
|
+
console.error(err(`Unknown key: "${key}". Run: pai project config --options`));
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (key === "env") config.env = {
|
|
702
|
+
...config.env ?? {},
|
|
703
|
+
...parseConfigValue("env", value)
|
|
704
|
+
};
|
|
705
|
+
else config[key] = parseConfigValue(key, value);
|
|
706
|
+
}
|
|
707
|
+
db.prepare("UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(config), now(), project.id);
|
|
708
|
+
console.log(ok(`Config updated for ${bold(project.slug)}`));
|
|
709
|
+
}
|
|
710
|
+
if (opts.unset?.length) {
|
|
711
|
+
for (const key of opts.unset) if (key.startsWith("env.")) {
|
|
712
|
+
const envKey = key.substring(4);
|
|
713
|
+
if (config.env) delete config.env[envKey];
|
|
714
|
+
} else delete config[key];
|
|
715
|
+
db.prepare("UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(config), now(), project.id);
|
|
716
|
+
console.log(ok(`Config updated for ${bold(project.slug)}`));
|
|
717
|
+
}
|
|
718
|
+
const effective = {
|
|
719
|
+
...getGlobalDefaults(),
|
|
720
|
+
...config
|
|
721
|
+
};
|
|
722
|
+
const aliases = getProjectAliases(db, project.id);
|
|
723
|
+
if (opts.json) {
|
|
724
|
+
console.log(JSON.stringify({
|
|
725
|
+
project: project.slug,
|
|
726
|
+
names: aliases,
|
|
727
|
+
root_path: project.root_path,
|
|
728
|
+
config,
|
|
729
|
+
global_defaults: getGlobalDefaults(),
|
|
730
|
+
effective
|
|
731
|
+
}, null, 2));
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
console.log();
|
|
735
|
+
console.log(header(` Config: ${project.slug}`));
|
|
736
|
+
if (aliases.length) console.log(dim(` Names: ${aliases.join(", ")}`));
|
|
737
|
+
console.log(dim(` Path: ${project.root_path}`));
|
|
738
|
+
console.log();
|
|
739
|
+
if (Object.keys(config).length === 0) console.log(dim(" No project-specific config. Using global defaults."));
|
|
740
|
+
else {
|
|
741
|
+
console.log(bold(" Project config:"));
|
|
742
|
+
for (const [key, value] of Object.entries(config)) {
|
|
743
|
+
const display = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
744
|
+
console.log(` ${bold(key.padEnd(14))} ${display}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
const globalDefaults = getGlobalDefaults();
|
|
748
|
+
if (Object.keys(globalDefaults).length > 0) {
|
|
749
|
+
console.log();
|
|
750
|
+
console.log(dim(" Global defaults (overridden by project config):"));
|
|
751
|
+
for (const [key, value] of Object.entries(globalDefaults)) {
|
|
752
|
+
const overridden = key in config;
|
|
753
|
+
const display = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
754
|
+
console.log(` ${dim(key.padEnd(14))} ${overridden ? chalk.strikethrough(display) + " " + dim("(overridden)") : display}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
console.log();
|
|
758
|
+
console.log(bold(" Effective (what AIBroker uses):"));
|
|
759
|
+
for (const [key, value] of Object.entries(effective)) {
|
|
760
|
+
const display = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
761
|
+
console.log(` ${bold(key.padEnd(14))} ${display}`);
|
|
762
|
+
}
|
|
763
|
+
console.log();
|
|
764
|
+
}
|
|
355
765
|
function cmdEdit(db, slug, opts) {
|
|
356
766
|
const project = requireProject(db, slug);
|
|
357
767
|
if (!opts.displayName && !opts.type) {
|
|
@@ -740,6 +1150,18 @@ function registerProjectCommands(projectCmd, getDb) {
|
|
|
740
1150
|
projectCmd.command("go <query>").description("Print the root path for a project by slug, partial name, or fuzzy match.\nDesigned for shell integration: cd $(pai project go <query>)\nOr set a shell alias: alias pcd='cd $(pai project go)'").action((query) => {
|
|
741
1151
|
cmdGo(getDb(), query);
|
|
742
1152
|
});
|
|
1153
|
+
projectCmd.command("name <identifier> <shortname>").description("Give a project a short name for quick access (used by AIBroker to launch sessions)").option("--permission <level>", "Permission level: full | trusted | default (or raw CLI flags)").action((identifier, shortname, opts) => {
|
|
1154
|
+
cmdName(getDb(), identifier, shortname, opts);
|
|
1155
|
+
});
|
|
1156
|
+
projectCmd.command("unname <shortname>").description("Remove a project's short name").action((shortname) => {
|
|
1157
|
+
cmdUnname(getDb(), shortname);
|
|
1158
|
+
});
|
|
1159
|
+
projectCmd.command("names").description("List named projects (your curated shortlist)").option("--json", "Output JSON for AIBroker consumption").action((opts) => {
|
|
1160
|
+
cmdNames(getDb(), opts);
|
|
1161
|
+
});
|
|
1162
|
+
projectCmd.command("config [identifier]").description("View or modify session launch config for a project.\nUse --options to discover available keys and presets.\nUse --defaults to manage global defaults for new sessions.").option("--set <key=value...>", "Set config values (repeatable)", (v, prev) => [...prev, v], []).option("--unset <key...>", "Remove config keys (repeatable, use env.KEY for env vars)", (v, prev) => [...prev, v], []).option("--preset <name>", "Apply a permission preset: full | trusted | default").option("--defaults", "Manage global session defaults instead of a project").option("--options", "List available config keys and presets").option("--json", "Output JSON").option("--reset", "Reset config to empty (inherit global defaults)").action((identifier, opts) => {
|
|
1163
|
+
cmdConfig(getDb(), identifier, opts);
|
|
1164
|
+
});
|
|
743
1165
|
}
|
|
744
1166
|
|
|
745
1167
|
//#endregion
|
|
@@ -1810,7 +2232,7 @@ function cmdActive(db, opts) {
|
|
|
1810
2232
|
}
|
|
1811
2233
|
async function cmdAutoRoute(opts) {
|
|
1812
2234
|
const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("../auto-route-BG6I_4B1.mjs");
|
|
1813
|
-
const { openRegistry } = await import("../db-
|
|
2235
|
+
const { openRegistry } = await import("../db-BtuN768f.mjs").then((n) => n.t);
|
|
1814
2236
|
const { createStorageBackend } = await import("../factory-Bzcy70G9.mjs").then((n) => n.n);
|
|
1815
2237
|
const { loadConfig } = await import("../config-Cf92lGX_.mjs").then((n) => n.r);
|
|
1816
2238
|
const config = loadConfig();
|
|
@@ -3737,7 +4159,7 @@ function cmdLogs(opts) {
|
|
|
3737
4159
|
}
|
|
3738
4160
|
function registerDaemonCommands(daemonCmd) {
|
|
3739
4161
|
daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
|
|
3740
|
-
const { serve } = await import("../daemon-
|
|
4162
|
+
const { serve } = await import("../daemon-2ND5WO2j.mjs").then((n) => n.t);
|
|
3741
4163
|
const { loadConfig: lc, ensureConfigDir } = await import("../config-Cf92lGX_.mjs").then((n) => n.r);
|
|
3742
4164
|
ensureConfigDir();
|
|
3743
4165
|
await serve(lc());
|
|
@@ -4827,6 +5249,56 @@ async function stepAiSteeringRules(rl) {
|
|
|
4827
5249
|
return true;
|
|
4828
5250
|
}
|
|
4829
5251
|
/**
|
|
5252
|
+
* Step 6b: CORE skill installation (~/.claude/Skills/CORE/)
|
|
5253
|
+
*
|
|
5254
|
+
* Installs the CORE skill template and supporting docs from templates/skills/CORE/
|
|
5255
|
+
* to ~/.claude/Skills/CORE/. Also creates the user skills directory at
|
|
5256
|
+
* ~/.claude/Skills/user/ if it doesn't exist.
|
|
5257
|
+
*/
|
|
5258
|
+
async function stepCoreSkill(rl) {
|
|
5259
|
+
section("Step 6b: CORE Skill Installation");
|
|
5260
|
+
line$1();
|
|
5261
|
+
line$1(" The CORE skill defines PAI's identity, response format, session commands,");
|
|
5262
|
+
line$1(" compaction resilience, and operating principles. It auto-loads at session start.");
|
|
5263
|
+
line$1();
|
|
5264
|
+
const coreSrcDir = join(getTemplatesDir$1(), "skills", "CORE");
|
|
5265
|
+
if (!existsSync(coreSrcDir)) {
|
|
5266
|
+
console.log(c.warn("CORE skill template not found: " + coreSrcDir));
|
|
5267
|
+
console.log(c.dim(" Skipping CORE skill installation."));
|
|
5268
|
+
return false;
|
|
5269
|
+
}
|
|
5270
|
+
const skillsDir = join(homedir(), ".claude", "Skills");
|
|
5271
|
+
const coreDestDir = join(skillsDir, "CORE");
|
|
5272
|
+
const userSkillsDir = join(skillsDir, "user");
|
|
5273
|
+
if (existsSync(join(coreDestDir, "SKILL.md"))) {
|
|
5274
|
+
console.log(c.dim(" Found existing CORE skill at ~/.claude/Skills/CORE/"));
|
|
5275
|
+
line$1();
|
|
5276
|
+
if (!await promptYesNo(rl, "Update ~/.claude/Skills/CORE/ with the latest CORE skill templates?", true)) {
|
|
5277
|
+
console.log(c.dim(" Keeping existing CORE skill unchanged."));
|
|
5278
|
+
if (!existsSync(userSkillsDir)) {
|
|
5279
|
+
mkdirSync(userSkillsDir, { recursive: true });
|
|
5280
|
+
console.log(c.ok("Created ~/.claude/Skills/user/ for custom skills"));
|
|
5281
|
+
}
|
|
5282
|
+
return false;
|
|
5283
|
+
}
|
|
5284
|
+
} else if (!await promptYesNo(rl, "Install CORE skill to ~/.claude/Skills/CORE/?", true)) {
|
|
5285
|
+
console.log(c.dim(" Skipping CORE skill installation."));
|
|
5286
|
+
return false;
|
|
5287
|
+
}
|
|
5288
|
+
if (!existsSync(coreDestDir)) mkdirSync(coreDestDir, { recursive: true });
|
|
5289
|
+
if (!existsSync(userSkillsDir)) mkdirSync(userSkillsDir, { recursive: true });
|
|
5290
|
+
const files = readdirSync(coreSrcDir).filter((f) => f.endsWith(".md"));
|
|
5291
|
+
let copied = 0;
|
|
5292
|
+
for (const file of files) {
|
|
5293
|
+
copyFileSync(join(coreSrcDir, file), join(coreDestDir, file));
|
|
5294
|
+
copied++;
|
|
5295
|
+
}
|
|
5296
|
+
line$1();
|
|
5297
|
+
console.log(c.ok(`Installed ${copied} CORE skill files to ~/.claude/Skills/CORE/`));
|
|
5298
|
+
console.log(c.ok("Created ~/.claude/Skills/user/ for custom skills"));
|
|
5299
|
+
return true;
|
|
5300
|
+
}
|
|
5301
|
+
/**
|
|
4830
5302
|
* Step 7: Hook scripts (pre-compact, session-stop, statusline)
|
|
4831
5303
|
*/
|
|
4832
5304
|
async function stepHooks(rl) {
|
|
@@ -5266,7 +5738,7 @@ async function stepInitialIndex(rl) {
|
|
|
5266
5738
|
/**
|
|
5267
5739
|
* Step 13: Summary and next steps
|
|
5268
5740
|
*/
|
|
5269
|
-
function stepSummary(configUpdates, claudeMdGenerated, paiSkillInstalled, aiSteeringRulesInstalled, hooksInstalled, tsHooksInstalled, settingsPatched, daName, daemonInstalled, mcpRegistered) {
|
|
5741
|
+
function stepSummary(configUpdates, claudeMdGenerated, paiSkillInstalled, aiSteeringRulesInstalled, coreSkillInstalled, hooksInstalled, tsHooksInstalled, settingsPatched, daName, daemonInstalled, mcpRegistered) {
|
|
5270
5742
|
section("Setup Complete");
|
|
5271
5743
|
line$1();
|
|
5272
5744
|
console.log(c.ok("PAI Knowledge OS is configured!"));
|
|
@@ -5280,6 +5752,7 @@ function stepSummary(configUpdates, claudeMdGenerated, paiSkillInstalled, aiStee
|
|
|
5280
5752
|
console.log(chalk.dim(" CLAUDE.md: ") + chalk.cyan(claudeMdGenerated ? "~/.claude/CLAUDE.md (generated)" : "(unchanged)"));
|
|
5281
5753
|
console.log(chalk.dim(" PAI skill: ") + chalk.cyan(paiSkillInstalled ? "~/.claude/skills/PAI/SKILL.md (installed)" : "(unchanged)"));
|
|
5282
5754
|
console.log(chalk.dim(" Steering rules: ") + chalk.cyan(aiSteeringRulesInstalled ? "~/.claude/skills/PAI/AI-STEERING-RULES.md (installed)" : "(unchanged)"));
|
|
5755
|
+
console.log(chalk.dim(" CORE skill: ") + chalk.cyan(coreSkillInstalled ? "~/.claude/Skills/CORE/ (installed)" : "(unchanged)"));
|
|
5283
5756
|
console.log(chalk.dim(" Hooks (shell): ") + chalk.cyan(hooksInstalled ? "pai-pre-compact.sh, pai-session-stop.sh (installed)" : "(unchanged)"));
|
|
5284
5757
|
console.log(chalk.dim(" Hooks (TS): ") + chalk.cyan(tsHooksInstalled ? "14 .mjs hooks installed to ~/.claude/Hooks/" : "(unchanged)"));
|
|
5285
5758
|
console.log(chalk.dim(" Assistant name: ") + chalk.cyan(daName));
|
|
@@ -5340,6 +5813,7 @@ async function runSetup() {
|
|
|
5340
5813
|
const claudeMdGenerated = await stepClaudeMd(rl);
|
|
5341
5814
|
const paiSkillInstalled = await stepPaiSkill(rl);
|
|
5342
5815
|
const aiSteeringRulesInstalled = await stepAiSteeringRules(rl);
|
|
5816
|
+
const coreSkillInstalled = await stepCoreSkill(rl);
|
|
5343
5817
|
const hooksInstalled = await stepHooks(rl);
|
|
5344
5818
|
const tsHooksInstalled = await stepTsHooks(rl);
|
|
5345
5819
|
const daName = await stepDaName(rl);
|
|
@@ -5355,7 +5829,7 @@ async function runSetup() {
|
|
|
5355
5829
|
line$1();
|
|
5356
5830
|
console.log(c.ok("Configuration saved."));
|
|
5357
5831
|
await stepInitialIndex(rl);
|
|
5358
|
-
stepSummary(allUpdates, claudeMdGenerated, paiSkillInstalled, aiSteeringRulesInstalled, hooksInstalled, tsHooksInstalled, settingsPatched, daName, daemonInstalled, mcpRegistered);
|
|
5832
|
+
stepSummary(allUpdates, claudeMdGenerated, paiSkillInstalled, aiSteeringRulesInstalled, coreSkillInstalled, hooksInstalled, tsHooksInstalled, settingsPatched, daName, daemonInstalled, mcpRegistered);
|
|
5359
5833
|
} finally {
|
|
5360
5834
|
rl.close();
|
|
5361
5835
|
}
|