portable-agent-layer 0.1.0 → 0.2.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.
- package/README.md +91 -0
- package/package.json +1 -2
- package/src/cli/index.ts +141 -77
- package/bin/pal +0 -24
- package/bin/pal.bat +0 -8
- package/bin/pal.ps1 +0 -30
package/README.md
CHANGED
|
@@ -26,6 +26,91 @@ With PAL, you can:
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
### Prerequisites
|
|
32
|
+
|
|
33
|
+
- [Bun](https://bun.sh) >= 1.3.0
|
|
34
|
+
|
|
35
|
+
### Package mode (recommended)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bun add -g portable-agent-layer
|
|
39
|
+
pal cli init
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Repo mode (for development / contributors)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/kovrichard/portable-agent-layer.git
|
|
46
|
+
cd portable-agent-layer
|
|
47
|
+
bun install
|
|
48
|
+
bun run install:all
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
In repo mode, add an alias to your shell profile:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
alias pal="bun run ~/path/to/portable-agent-layer/src/cli/index.ts"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pal cli init # scaffold home, install hooks for all targets
|
|
63
|
+
pal # start a Claude session (with session summary on exit)
|
|
64
|
+
pal cli status # check your setup
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Commands
|
|
70
|
+
|
|
71
|
+
| Command | Description |
|
|
72
|
+
|---------|-------------|
|
|
73
|
+
| `pal` | Start a Claude session with session summary on exit |
|
|
74
|
+
| `pal cli init` | Scaffold PAL home directory and install hooks |
|
|
75
|
+
| `pal cli install` | Register hooks/skills for targets |
|
|
76
|
+
| `pal cli uninstall` | Remove hooks/skills for targets |
|
|
77
|
+
| `pal cli export` | Export user state (telos, memory) to a zip |
|
|
78
|
+
| `pal cli import` | Import user state from a zip |
|
|
79
|
+
| `pal cli status` | Show current PAL configuration |
|
|
80
|
+
|
|
81
|
+
### Target flags
|
|
82
|
+
|
|
83
|
+
`init`, `install`, and `uninstall` accept target flags:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pal cli install --claude # Claude Code only
|
|
87
|
+
pal cli install --opencode # opencode only
|
|
88
|
+
pal cli install # both (default)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Environment variables
|
|
94
|
+
|
|
95
|
+
### Required
|
|
96
|
+
|
|
97
|
+
| Variable | Description |
|
|
98
|
+
|----------|-------------|
|
|
99
|
+
| `ANTHROPIC_API_KEY` | Required for PAL's hook inference (sentiment analysis, session naming). Uses Haiku for low-cost background calls. |
|
|
100
|
+
|
|
101
|
+
### Optional
|
|
102
|
+
|
|
103
|
+
| Variable | Description |
|
|
104
|
+
|----------|-------------|
|
|
105
|
+
| `GEMINI_API_KEY` | For YouTube video analysis skill |
|
|
106
|
+
| `PAL_HOME` | Override user state directory (default: `~/.pal` or repo root) |
|
|
107
|
+
| `PAL_PKG` | Override package root |
|
|
108
|
+
| `PAL_CLAUDE_DIR` | Override Claude config dir (default: `~/.claude`) |
|
|
109
|
+
| `PAL_OPENCODE_DIR` | Override opencode config dir (default: `~/.config/opencode`) |
|
|
110
|
+
| `PAL_AGENTS_DIR` | Override agents dir (default: `~/.agents`) |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
29
114
|
## Core idea
|
|
30
115
|
|
|
31
116
|
PAL stands for **Portable Agent Layer**.
|
|
@@ -78,3 +163,9 @@ PAL is for people who want:
|
|
|
78
163
|
- to move between machines without rebuilding everything
|
|
79
164
|
- a durable way to store and reuse context
|
|
80
165
|
- an open foundation for portable agent workflows
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
[MIT](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portable-agent-layer",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"src/",
|
|
11
11
|
"assets/",
|
|
12
|
-
"bin/",
|
|
13
12
|
"README.md",
|
|
14
13
|
"LICENSE"
|
|
15
14
|
],
|
package/src/cli/index.ts
CHANGED
|
@@ -2,23 +2,129 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* PAL CLI — Portable Agent Layer
|
|
4
4
|
*
|
|
5
|
-
* Usage:
|
|
5
|
+
* Usage:
|
|
6
|
+
* pal [claude-args...] Start a Claude session with session summary on exit
|
|
7
|
+
* pal cli <command> [options] Admin commands
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
* init
|
|
9
|
-
* install
|
|
10
|
-
* uninstall
|
|
11
|
-
* export
|
|
12
|
-
* import
|
|
13
|
-
* status
|
|
9
|
+
* Admin commands (pal cli ...):
|
|
10
|
+
* init Scaffold PAL home, install hooks for all targets
|
|
11
|
+
* install [--claude] [--opencode] Register hooks/skills for targets
|
|
12
|
+
* uninstall [--claude] [--opencode] Remove hooks/skills for targets
|
|
13
|
+
* export [path] [--dry-run] Export user state to zip
|
|
14
|
+
* import [path] [--dry-run] Import user state from zip
|
|
15
|
+
* status Show current PAL configuration
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
|
-
import {
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
20
|
+
import { homedir } from "node:os";
|
|
17
21
|
import { resolve } from "node:path";
|
|
18
22
|
import { palHome, palPkg, platform } from "../hooks/lib/paths";
|
|
19
23
|
import { log } from "../targets/lib";
|
|
20
24
|
|
|
21
|
-
const
|
|
25
|
+
const allArgs = process.argv.slice(2);
|
|
26
|
+
|
|
27
|
+
// ── Route: pal cli <command> or pal [claude-args] ──
|
|
28
|
+
|
|
29
|
+
if (allArgs[0] === "cli") {
|
|
30
|
+
const [, command, ...args] = allArgs;
|
|
31
|
+
await runCli(command, args);
|
|
32
|
+
} else if (allArgs[0] === "--help" || allArgs[0] === "-h" || allArgs[0] === "help") {
|
|
33
|
+
showHelp();
|
|
34
|
+
} else {
|
|
35
|
+
await session(allArgs);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Session: pal [claude-args] ──
|
|
39
|
+
|
|
40
|
+
async function session(claudeArgs: string[]) {
|
|
41
|
+
// Run claude with all args, inheriting stdio for interactive TTY
|
|
42
|
+
const result = spawnSync("claude", claudeArgs, {
|
|
43
|
+
stdio: "inherit",
|
|
44
|
+
shell: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const exitCode = result.status ?? 1;
|
|
48
|
+
|
|
49
|
+
// Find the most recent transcript and extract session ID
|
|
50
|
+
try {
|
|
51
|
+
const projectsDir = resolve(homedir(), ".claude", "projects");
|
|
52
|
+
if (!existsSync(projectsDir)) process.exit(exitCode);
|
|
53
|
+
|
|
54
|
+
// Find most recently modified .jsonl file
|
|
55
|
+
let latestFile = "";
|
|
56
|
+
let latestMtime = 0;
|
|
57
|
+
|
|
58
|
+
for (const project of readdirSync(projectsDir, { withFileTypes: true })) {
|
|
59
|
+
if (!project.isDirectory()) continue;
|
|
60
|
+
const dir = resolve(projectsDir, project.name);
|
|
61
|
+
for (const file of readdirSync(dir)) {
|
|
62
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
63
|
+
const filepath = resolve(dir, file);
|
|
64
|
+
const { mtimeMs } = statSync(filepath);
|
|
65
|
+
if (mtimeMs > latestMtime) {
|
|
66
|
+
latestMtime = mtimeMs;
|
|
67
|
+
latestFile = filepath;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (latestFile) {
|
|
73
|
+
const content = readFileSync(latestFile, "utf-8").trim();
|
|
74
|
+
const lastLine = content.split("\n").pop();
|
|
75
|
+
if (lastLine) {
|
|
76
|
+
const sessionId = JSON.parse(lastLine).sessionId;
|
|
77
|
+
if (sessionId) {
|
|
78
|
+
const summaryScript = resolve(palPkg(), "src", "tools", "session-summary.ts");
|
|
79
|
+
spawnSync("bun", ["run", summaryScript, "--", "--session", sessionId], {
|
|
80
|
+
stdio: "inherit",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Silently ignore summary errors
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
process.exit(exitCode);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── CLI dispatcher ──
|
|
93
|
+
|
|
94
|
+
async function runCli(command: string | undefined, args: string[]) {
|
|
95
|
+
switch (command) {
|
|
96
|
+
case "init":
|
|
97
|
+
await init(args);
|
|
98
|
+
break;
|
|
99
|
+
case "install":
|
|
100
|
+
banner();
|
|
101
|
+
await install(parseTargets(args));
|
|
102
|
+
break;
|
|
103
|
+
case "uninstall":
|
|
104
|
+
await uninstall(args);
|
|
105
|
+
break;
|
|
106
|
+
case "export":
|
|
107
|
+
await exportState(args);
|
|
108
|
+
break;
|
|
109
|
+
case "import":
|
|
110
|
+
await importState(args);
|
|
111
|
+
break;
|
|
112
|
+
case "status":
|
|
113
|
+
await status();
|
|
114
|
+
break;
|
|
115
|
+
case "--help":
|
|
116
|
+
case "-h":
|
|
117
|
+
case "help":
|
|
118
|
+
showHelp();
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
if (command) log.error(`Unknown command: ${command}`);
|
|
122
|
+
showHelp();
|
|
123
|
+
process.exit(command ? 1 : 0);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Helpers ──
|
|
22
128
|
|
|
23
129
|
function banner() {
|
|
24
130
|
console.log("");
|
|
@@ -31,15 +137,18 @@ function banner() {
|
|
|
31
137
|
|
|
32
138
|
function showHelp() {
|
|
33
139
|
console.log(`
|
|
34
|
-
Usage:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
140
|
+
Usage:
|
|
141
|
+
pal [claude-args...] Start a Claude session
|
|
142
|
+
pal cli <command> [options] Admin commands
|
|
143
|
+
|
|
144
|
+
Admin commands:
|
|
145
|
+
pal cli init [--claude] [--opencode] Scaffold and install (default: all)
|
|
146
|
+
pal cli install [--claude] [--opencode] Register hooks for targets
|
|
147
|
+
pal cli uninstall [--claude] [--opencode] Remove hooks for targets
|
|
148
|
+
pal cli export [path] [--dry-run] Export state to zip
|
|
149
|
+
pal cli import [path] [--dry-run] Import state from zip
|
|
150
|
+
pal cli status Show PAL configuration
|
|
151
|
+
|
|
43
152
|
Environment:
|
|
44
153
|
PAL_HOME Override user state directory (default: ~/.pal or repo root)
|
|
45
154
|
PAL_PKG Override package root
|
|
@@ -53,8 +162,6 @@ function parseTargets(args: string[]): {
|
|
|
53
162
|
claude: boolean;
|
|
54
163
|
opencode: boolean;
|
|
55
164
|
} {
|
|
56
|
-
if (args.length === 0) return { claude: true, opencode: true };
|
|
57
|
-
|
|
58
165
|
let claude = false;
|
|
59
166
|
let opencode = false;
|
|
60
167
|
for (const arg of args) {
|
|
@@ -65,14 +172,13 @@ function parseTargets(args: string[]): {
|
|
|
65
172
|
opencode = true;
|
|
66
173
|
}
|
|
67
174
|
}
|
|
68
|
-
// If no target flags, default to all
|
|
69
175
|
if (!claude && !opencode) return { claude: true, opencode: true };
|
|
70
176
|
return { claude, opencode };
|
|
71
177
|
}
|
|
72
178
|
|
|
73
179
|
// ── Commands ──
|
|
74
180
|
|
|
75
|
-
async function init() {
|
|
181
|
+
async function init(args: string[]) {
|
|
76
182
|
const { ensureSetupState, isSetupComplete } = await import("../hooks/lib/setup");
|
|
77
183
|
const { scaffoldTelos } = await import("../targets/lib");
|
|
78
184
|
|
|
@@ -82,7 +188,6 @@ async function init() {
|
|
|
82
188
|
const isRepo = existsSync(resolve(palPkg(), ".palroot"));
|
|
83
189
|
|
|
84
190
|
if (!isRepo) {
|
|
85
|
-
// Package mode — scaffold ~/.pal/
|
|
86
191
|
log.info(`Creating PAL home at ${home}`);
|
|
87
192
|
mkdirSync(resolve(home, "telos"), { recursive: true });
|
|
88
193
|
mkdirSync(resolve(home, "memory"), { recursive: true });
|
|
@@ -91,8 +196,7 @@ async function init() {
|
|
|
91
196
|
scaffoldTelos();
|
|
92
197
|
ensureSetupState();
|
|
93
198
|
|
|
94
|
-
|
|
95
|
-
await install(targets);
|
|
199
|
+
await install(parseTargets(args));
|
|
96
200
|
|
|
97
201
|
console.log("");
|
|
98
202
|
const state = ensureSetupState();
|
|
@@ -101,16 +205,14 @@ async function init() {
|
|
|
101
205
|
}
|
|
102
206
|
}
|
|
103
207
|
|
|
104
|
-
async function install(targets
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (t.claude) {
|
|
208
|
+
async function install(targets: { claude: boolean; opencode: boolean }) {
|
|
209
|
+
if (targets.claude) {
|
|
108
210
|
console.log("━━━ Claude Code ━━━");
|
|
109
211
|
await import("../targets/claude/install");
|
|
110
212
|
console.log("");
|
|
111
213
|
}
|
|
112
214
|
|
|
113
|
-
if (
|
|
215
|
+
if (targets.opencode) {
|
|
114
216
|
console.log("━━━ opencode ━━━");
|
|
115
217
|
await import("../targets/opencode/install");
|
|
116
218
|
console.log("");
|
|
@@ -119,7 +221,7 @@ async function install(targets?: { claude: boolean; opencode: boolean }) {
|
|
|
119
221
|
log.success("Done. Existing config was preserved — only new entries were added.");
|
|
120
222
|
}
|
|
121
223
|
|
|
122
|
-
async function uninstall() {
|
|
224
|
+
async function uninstall(args: string[]) {
|
|
123
225
|
const targets = parseTargets(args);
|
|
124
226
|
|
|
125
227
|
if (targets.claude) {
|
|
@@ -139,13 +241,13 @@ async function uninstall() {
|
|
|
139
241
|
);
|
|
140
242
|
}
|
|
141
243
|
|
|
142
|
-
async function exportState() {
|
|
244
|
+
async function exportState(args: string[]) {
|
|
143
245
|
const { collectExportFiles, exportZip, timestamp } = await import(
|
|
144
246
|
"../hooks/lib/export"
|
|
145
247
|
);
|
|
146
248
|
|
|
147
249
|
const dryRun = args.includes("--dry-run");
|
|
148
|
-
const pathArg = args.find((a) => !a.startsWith("-")
|
|
250
|
+
const pathArg = args.find((a) => !a.startsWith("-"));
|
|
149
251
|
const outputPath = pathArg || resolve(palHome(), `pal-export-${timestamp()}.zip`);
|
|
150
252
|
|
|
151
253
|
if (dryRun) {
|
|
@@ -166,14 +268,14 @@ async function exportState() {
|
|
|
166
268
|
}
|
|
167
269
|
}
|
|
168
270
|
|
|
169
|
-
async function importState() {
|
|
170
|
-
const {
|
|
271
|
+
async function importState(args: string[]) {
|
|
272
|
+
const { statSync } = await import("node:fs");
|
|
171
273
|
const { createInterface } = await import("node:readline");
|
|
172
274
|
const AdmZip = (await import("adm-zip")).default;
|
|
173
275
|
|
|
174
276
|
const home = palHome();
|
|
175
277
|
const dryRun = args.includes("--dry-run");
|
|
176
|
-
const pathArg = args.find((a) => !a.startsWith("-")
|
|
278
|
+
const pathArg = args.find((a) => !a.startsWith("-"));
|
|
177
279
|
|
|
178
280
|
function findLatest(): string | null {
|
|
179
281
|
const candidates: string[] = [];
|
|
@@ -214,7 +316,7 @@ async function importState() {
|
|
|
214
316
|
} else {
|
|
215
317
|
const latest = findLatest();
|
|
216
318
|
if (!latest) {
|
|
217
|
-
log.error("No export or backup files found. Provide a path: pal import <path>");
|
|
319
|
+
log.error("No export or backup files found. Provide a path: pal cli import <path>");
|
|
218
320
|
process.exit(1);
|
|
219
321
|
}
|
|
220
322
|
console.log(`Found: ${latest}`);
|
|
@@ -254,13 +356,11 @@ async function importState() {
|
|
|
254
356
|
} else {
|
|
255
357
|
zip.extractAllTo(home, true);
|
|
256
358
|
console.log(`Imported ${entries.length} files → ${home}`);
|
|
257
|
-
log.info("Run 'pal install' to re-register hooks.");
|
|
359
|
+
log.info("Run 'pal cli install' to re-register hooks.");
|
|
258
360
|
}
|
|
259
361
|
}
|
|
260
362
|
|
|
261
363
|
async function status() {
|
|
262
|
-
const { existsSync, readdirSync, readFileSync } = await import("node:fs");
|
|
263
|
-
|
|
264
364
|
const home = palHome();
|
|
265
365
|
const pkg = palPkg();
|
|
266
366
|
const isRepo = existsSync(resolve(pkg, ".palroot"));
|
|
@@ -274,13 +374,11 @@ async function status() {
|
|
|
274
374
|
log.info(`Home: ${home}`);
|
|
275
375
|
console.log("");
|
|
276
376
|
|
|
277
|
-
// Platform dirs
|
|
278
377
|
log.info(`Claude: ${platform.claudeDir()}`);
|
|
279
378
|
log.info(`opencode: ${platform.opencodeDir()}`);
|
|
280
379
|
log.info(`Agents: ${platform.agentsDir()}`);
|
|
281
380
|
console.log("");
|
|
282
381
|
|
|
283
|
-
// Counts
|
|
284
382
|
const count = (dir: string, ext?: string) => {
|
|
285
383
|
try {
|
|
286
384
|
const files = readdirSync(dir);
|
|
@@ -298,7 +396,6 @@ async function status() {
|
|
|
298
396
|
const agentsDir = resolve(platform.claudeDir(), "agents");
|
|
299
397
|
log.info(`Agents: ${count(agentsDir, ".md")} installed`);
|
|
300
398
|
|
|
301
|
-
// Check if hooks are registered
|
|
302
399
|
const settingsPath = resolve(platform.claudeDir(), "settings.json");
|
|
303
400
|
try {
|
|
304
401
|
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
@@ -309,36 +406,3 @@ async function status() {
|
|
|
309
406
|
}
|
|
310
407
|
console.log("");
|
|
311
408
|
}
|
|
312
|
-
|
|
313
|
-
// ── Dispatch ──
|
|
314
|
-
|
|
315
|
-
switch (command) {
|
|
316
|
-
case "init":
|
|
317
|
-
await init();
|
|
318
|
-
break;
|
|
319
|
-
case "install":
|
|
320
|
-
banner();
|
|
321
|
-
await install();
|
|
322
|
-
break;
|
|
323
|
-
case "uninstall":
|
|
324
|
-
await uninstall();
|
|
325
|
-
break;
|
|
326
|
-
case "export":
|
|
327
|
-
await exportState();
|
|
328
|
-
break;
|
|
329
|
-
case "import":
|
|
330
|
-
await importState();
|
|
331
|
-
break;
|
|
332
|
-
case "status":
|
|
333
|
-
await status();
|
|
334
|
-
break;
|
|
335
|
-
case "--help":
|
|
336
|
-
case "-h":
|
|
337
|
-
case "help":
|
|
338
|
-
showHelp();
|
|
339
|
-
break;
|
|
340
|
-
default:
|
|
341
|
-
if (command) log.error(`Unknown command: ${command}`);
|
|
342
|
-
showHelp();
|
|
343
|
-
process.exit(command ? 1 : 0);
|
|
344
|
-
}
|
package/bin/pal
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Jarvis — Claude Code wrapper with session summary on exit.
|
|
3
|
-
#
|
|
4
|
-
# After Claude exits, finds the most recently modified transcript JSONL
|
|
5
|
-
# in ~/.claude/projects/ and extracts the sessionId from its last line.
|
|
6
|
-
|
|
7
|
-
PAL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
|
-
|
|
9
|
-
# Run Claude (blocking — keeps the interactive terminal)
|
|
10
|
-
claude "$@"
|
|
11
|
-
EXIT_CODE=$?
|
|
12
|
-
|
|
13
|
-
# Find the most recently modified transcript and extract its session ID
|
|
14
|
-
LATEST=$(find "$HOME/.claude/projects" -name '*.jsonl' -type f -print0 2>/dev/null \
|
|
15
|
-
| xargs -0 ls -t 2>/dev/null | head -1)
|
|
16
|
-
|
|
17
|
-
if [ -n "$LATEST" ]; then
|
|
18
|
-
SESSION_ID=$(tail -1 "$LATEST" | python3 -c "import sys,json; print(json.loads(sys.stdin.readline()).get('sessionId',''))" 2>/dev/null)
|
|
19
|
-
if [ -n "$SESSION_ID" ]; then
|
|
20
|
-
bun run "$PAL_DIR/src/tools/session-summary.ts" -- --session "$SESSION_ID" 2>/dev/null
|
|
21
|
-
fi
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
exit $EXIT_CODE
|
package/bin/pal.bat
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
@echo off
|
|
2
|
-
REM Jarvis — Claude Code wrapper with session summary on exit.
|
|
3
|
-
REM
|
|
4
|
-
REM Uses PowerShell to start Claude, capture its PID, read the session ID
|
|
5
|
-
REM from %USERPROFILE%\.claude\sessions\<PID>.json, then show a cost
|
|
6
|
-
REM summary after Claude exits.
|
|
7
|
-
|
|
8
|
-
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0pal.ps1" %*
|
package/bin/pal.ps1
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Jarvis — Claude Code wrapper with session summary on exit.
|
|
2
|
-
#
|
|
3
|
-
# After Claude exits, finds the most recently modified transcript JSONL
|
|
4
|
-
# in ~/.claude/projects/ and extracts the sessionId from its last line.
|
|
5
|
-
|
|
6
|
-
$palDir = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
7
|
-
|
|
8
|
-
# Run Claude (blocking — keeps the interactive terminal)
|
|
9
|
-
& claude @args
|
|
10
|
-
$exitCode = $LASTEXITCODE
|
|
11
|
-
|
|
12
|
-
# Find the most recently modified transcript and extract its session ID
|
|
13
|
-
$latest = Get-ChildItem "$env:USERPROFILE\.claude\projects\*\*.jsonl" -ErrorAction SilentlyContinue |
|
|
14
|
-
Sort-Object LastWriteTime -Descending |
|
|
15
|
-
Select-Object -First 1
|
|
16
|
-
|
|
17
|
-
if ($latest) {
|
|
18
|
-
$lastLine = Get-Content $latest.FullName -Tail 1 -ErrorAction SilentlyContinue
|
|
19
|
-
if ($lastLine) {
|
|
20
|
-
try {
|
|
21
|
-
$sessionId = ($lastLine | ConvertFrom-Json).sessionId
|
|
22
|
-
if ($sessionId) {
|
|
23
|
-
$summaryScript = Join-Path $palDir "src" "tools" "session-summary.ts"
|
|
24
|
-
& bun run $summaryScript -- --session $sessionId 2>$null
|
|
25
|
-
}
|
|
26
|
-
} catch {}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
exit $exitCode
|