portable-agent-layer 0.25.0 → 0.26.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.
- package/assets/skills/telos/tools/update-projects.ts +58 -53
- package/assets/templates/AGENTS.md.template +0 -1
- package/package.json +1 -1
- package/src/cli/index.ts +2 -8
- package/src/cli/setup-telos.ts +120 -0
- package/src/hooks/lib/claude-md.ts +1 -5
- package/src/hooks/lib/context.ts +4 -4
- package/src/hooks/lib/setup.ts +21 -75
- package/src/hooks/lib/token-usage.ts +2 -1
- package/src/tools/self-model.ts +3 -0
- package/src/tools/token-cost.ts +31 -10
|
@@ -38,64 +38,69 @@ function isoDate(): string {
|
|
|
38
38
|
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
console.error(
|
|
49
|
-
'\nExample: bun update-projects.ts my-proj "| my-proj | My Project | In progress | High | Notes |" "Added My Project"'
|
|
50
|
-
);
|
|
51
|
-
process.exit(1);
|
|
41
|
+
export interface UpsertProjectResult {
|
|
42
|
+
file: string;
|
|
43
|
+
id: string;
|
|
44
|
+
mode: "replaced" | "appended";
|
|
45
|
+
backed_up: boolean;
|
|
46
|
+
logged: boolean;
|
|
47
|
+
description: string;
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
50
|
+
export function upsertProject(
|
|
51
|
+
id: string,
|
|
52
|
+
row: string,
|
|
53
|
+
description: string
|
|
54
|
+
): UpsertProjectResult {
|
|
55
|
+
mkdirSync(BACKUPS_DIR, { recursive: true });
|
|
56
|
+
if (existsSync(PROJECTS_FILE)) {
|
|
57
|
+
const backupName = `PROJECTS-${timestamp()}.md`;
|
|
58
|
+
copyFileSync(PROJECTS_FILE, resolve(BACKUPS_DIR, backupName));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const existing = existsSync(PROJECTS_FILE) ? readFileSync(PROJECTS_FILE, "utf-8") : "";
|
|
62
|
+
|
|
63
|
+
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
64
|
+
const idPattern = new RegExp(`^\\|\\s*${escapedId}\\s*\\|.*$`, "m");
|
|
65
|
+
let mode: "replaced" | "appended";
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
if (idPattern.test(existing)) {
|
|
68
|
+
writeFileSync(PROJECTS_FILE, existing.replace(idPattern, row.trim()), "utf-8");
|
|
69
|
+
mode = "replaced";
|
|
70
|
+
} else {
|
|
71
|
+
const separator = existing.trim() ? "\n" : "";
|
|
72
|
+
writeFileSync(
|
|
73
|
+
PROJECTS_FILE,
|
|
74
|
+
`${existing.trimEnd()}${separator}${row.trim()}\n`,
|
|
75
|
+
"utf-8"
|
|
76
|
+
);
|
|
77
|
+
mode = "appended";
|
|
78
|
+
}
|
|
62
79
|
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
80
|
+
const logEntry = `- **${isoDate()}** — \`PROJECTS.md\` [${id}]: ${description}`;
|
|
81
|
+
const existingLog = existsSync(UPDATES_LOG)
|
|
82
|
+
? readFileSync(UPDATES_LOG, "utf-8")
|
|
83
|
+
: "# TELOS Updates\n";
|
|
84
|
+
writeFileSync(UPDATES_LOG, `${existingLog.trimEnd()}\n${logEntry}\n`, "utf-8");
|
|
66
85
|
|
|
67
|
-
|
|
68
|
-
const updated = existing.replace(idPattern, row.trim());
|
|
69
|
-
writeFileSync(PROJECTS_FILE, updated, "utf-8");
|
|
70
|
-
mode = "replaced";
|
|
71
|
-
} else {
|
|
72
|
-
const separator = existing.trim() ? "\n" : "";
|
|
73
|
-
writeFileSync(
|
|
74
|
-
PROJECTS_FILE,
|
|
75
|
-
`${existing.trimEnd()}${separator}${row.trim()}\n`,
|
|
76
|
-
"utf-8"
|
|
77
|
-
);
|
|
78
|
-
mode = "appended";
|
|
86
|
+
return { file: "PROJECTS.md", id, mode, backed_up: true, logged: true, description };
|
|
79
87
|
}
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
function run() {
|
|
90
|
+
const args = process.argv.slice(2);
|
|
91
|
+
const id = args[0];
|
|
92
|
+
const row = args[1];
|
|
93
|
+
const description = args[2];
|
|
94
|
+
|
|
95
|
+
if (!id || !row || !description) {
|
|
96
|
+
console.error('Usage: bun update-projects.ts <id> "<row>" "<description>"');
|
|
97
|
+
console.error(
|
|
98
|
+
'\nExample: bun update-projects.ts my-proj "| my-proj | My Project | In progress | High | Notes |" "Added My Project"'
|
|
99
|
+
);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(JSON.stringify(upsertProject(id, row, description), null, 2));
|
|
104
|
+
}
|
|
87
105
|
|
|
88
|
-
|
|
89
|
-
JSON.stringify(
|
|
90
|
-
{
|
|
91
|
-
file: "PROJECTS.md",
|
|
92
|
-
id,
|
|
93
|
-
mode,
|
|
94
|
-
backed_up: true,
|
|
95
|
-
logged: true,
|
|
96
|
-
description,
|
|
97
|
-
},
|
|
98
|
-
null,
|
|
99
|
-
2
|
|
100
|
-
)
|
|
101
|
-
);
|
|
106
|
+
if (import.meta.main) run();
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -574,7 +574,6 @@ function doctor(silent = false): DoctorResult {
|
|
|
574
574
|
// ── Commands ──
|
|
575
575
|
|
|
576
576
|
async function init(args: string[]) {
|
|
577
|
-
const { ensureSetupState, isSetupComplete } = await import("../hooks/lib/setup");
|
|
578
577
|
const { scaffoldTelos } = await import("../targets/lib");
|
|
579
578
|
|
|
580
579
|
banner();
|
|
@@ -591,17 +590,10 @@ async function init(args: string[]) {
|
|
|
591
590
|
mkdirSync(resolve(home, "memory"), { recursive: true });
|
|
592
591
|
|
|
593
592
|
scaffoldTelos();
|
|
594
|
-
ensureSetupState();
|
|
595
593
|
|
|
596
594
|
// Auto-detect available targets
|
|
597
595
|
const targets = resolveTargets(args, health);
|
|
598
596
|
await install(targets);
|
|
599
|
-
|
|
600
|
-
console.log("");
|
|
601
|
-
const state = ensureSetupState();
|
|
602
|
-
if (!isSetupComplete(state)) {
|
|
603
|
-
log.info("Start a session — PAL will guide you through first-run setup");
|
|
604
|
-
}
|
|
605
597
|
}
|
|
606
598
|
|
|
607
599
|
async function install(targets: Targets) {
|
|
@@ -620,9 +612,11 @@ async function install(targets: Targets) {
|
|
|
620
612
|
// Scaffold TELOS + PAL settings, then prompt for missing identity
|
|
621
613
|
const { scaffoldTelos, scaffoldPalSettings } = await import("../targets/lib");
|
|
622
614
|
const { promptIdentity } = await import("./setup-identity");
|
|
615
|
+
const { promptTelos } = await import("./setup-telos");
|
|
623
616
|
scaffoldTelos();
|
|
624
617
|
scaffoldPalSettings();
|
|
625
618
|
await promptIdentity();
|
|
619
|
+
await promptTelos();
|
|
626
620
|
|
|
627
621
|
if (targets.claude) {
|
|
628
622
|
console.log("━━━ Claude Code ━━━");
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive TELOS setup — prompts for personal context during `pal install`.
|
|
3
|
+
* Skips any step whose TELOS file already has real content.
|
|
4
|
+
* Projects use the upsertProject tool directly with a structured add-another loop.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { writeFileSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import * as clack from "@clack/prompts";
|
|
10
|
+
import { upsertProject } from "../../assets/skills/telos/tools/update-projects";
|
|
11
|
+
import { palHome } from "../hooks/lib/paths";
|
|
12
|
+
import { hasRealContent, SETUP_STEPS, STEP_ORDER } from "../hooks/lib/setup";
|
|
13
|
+
|
|
14
|
+
function toKebabCase(name: string): string {
|
|
15
|
+
return name
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
18
|
+
.replace(/^-|-$/g, "");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function promptProjectsLoop(): Promise<void> {
|
|
22
|
+
const addFirst = await clack.confirm({
|
|
23
|
+
message: "Do you want to add any projects now?",
|
|
24
|
+
initialValue: true,
|
|
25
|
+
});
|
|
26
|
+
if (clack.isCancel(addFirst) || !addFirst) return;
|
|
27
|
+
|
|
28
|
+
let addMore = true;
|
|
29
|
+
while (addMore) {
|
|
30
|
+
const name = await clack.text({
|
|
31
|
+
message: "Project name?",
|
|
32
|
+
placeholder: "e.g. PAL, My SaaS, Work Dashboard",
|
|
33
|
+
});
|
|
34
|
+
if (clack.isCancel(name)) return;
|
|
35
|
+
|
|
36
|
+
const status = await clack.select({
|
|
37
|
+
message: "Status?",
|
|
38
|
+
options: [
|
|
39
|
+
{ value: "Active", label: "Active" },
|
|
40
|
+
{ value: "Planning", label: "Planning" },
|
|
41
|
+
{ value: "Paused", label: "Paused" },
|
|
42
|
+
{ value: "Complete", label: "Complete" },
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
if (clack.isCancel(status)) return;
|
|
46
|
+
|
|
47
|
+
const priority = await clack.select({
|
|
48
|
+
message: "Priority?",
|
|
49
|
+
options: [
|
|
50
|
+
{ value: "High", label: "High" },
|
|
51
|
+
{ value: "Medium", label: "Medium" },
|
|
52
|
+
{ value: "Low", label: "Low" },
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
if (clack.isCancel(priority)) return;
|
|
56
|
+
|
|
57
|
+
const notes = await clack.text({
|
|
58
|
+
message: "Notes? (optional — leave blank to skip)",
|
|
59
|
+
placeholder: "e.g. Building the v2 API, blocked on design review",
|
|
60
|
+
});
|
|
61
|
+
if (clack.isCancel(notes)) return;
|
|
62
|
+
|
|
63
|
+
const id = toKebabCase(name as string);
|
|
64
|
+
const row = `| ${id} | ${name} | ${status} | ${priority} | ${notes || ""} |`;
|
|
65
|
+
upsertProject(id, row, `Added ${name} during PAL setup`);
|
|
66
|
+
clack.log.success(`Added: ${name}`);
|
|
67
|
+
|
|
68
|
+
const again = await clack.confirm({
|
|
69
|
+
message: "Add another project?",
|
|
70
|
+
initialValue: false,
|
|
71
|
+
});
|
|
72
|
+
if (clack.isCancel(again) || !again) addMore = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Prompt for missing TELOS context. Skips any step whose file already has real content. */
|
|
77
|
+
export async function promptTelos(): Promise<void> {
|
|
78
|
+
// Skip interactive prompts in non-TTY environments (tests, CI)
|
|
79
|
+
if (!process.stdin.isTTY) return;
|
|
80
|
+
|
|
81
|
+
const home = palHome();
|
|
82
|
+
const pending = STEP_ORDER.filter(
|
|
83
|
+
(key) => !hasRealContent(resolve(home, SETUP_STEPS[key].file))
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (pending.length === 0) {
|
|
87
|
+
clack.log.info("TELOS already configured");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
clack.intro("Personal Context Setup");
|
|
92
|
+
clack.note(
|
|
93
|
+
"Answer in a sentence or two — you can edit the files in ~/.pal/telos/ for more detail later.",
|
|
94
|
+
"Quick setup"
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
for (const key of pending) {
|
|
98
|
+
if (key === "projects") {
|
|
99
|
+
await promptProjectsLoop();
|
|
100
|
+
} else {
|
|
101
|
+
const step = SETUP_STEPS[key];
|
|
102
|
+
const title = key.charAt(0).toUpperCase() + key.slice(1);
|
|
103
|
+
|
|
104
|
+
const answer = await clack.text({
|
|
105
|
+
message: step.question,
|
|
106
|
+
placeholder: step.hint,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (clack.isCancel(answer)) {
|
|
110
|
+
clack.cancel("Setup cancelled");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const filePath = resolve(home, step.file);
|
|
115
|
+
writeFileSync(filePath, `# ${title}\n\n${answer}\n`, "utf-8");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
clack.outro("Personal context saved ✓");
|
|
120
|
+
}
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
} from "node:fs";
|
|
20
20
|
import { dirname, relative, resolve } from "node:path";
|
|
21
21
|
import { assets, ensureDir, paths, platform } from "./paths";
|
|
22
|
-
import { buildSetupPrompt, readSetupState } from "./setup";
|
|
23
22
|
|
|
24
23
|
const TEMPLATE_PATH = assets.agentsMdTemplate();
|
|
25
24
|
|
|
@@ -114,14 +113,11 @@ import { identity } from "./settings";
|
|
|
114
113
|
export function buildClaudeMd(): string {
|
|
115
114
|
const template = existsSync(TEMPLATE_PATH)
|
|
116
115
|
? readFileSync(TEMPLATE_PATH, "utf-8")
|
|
117
|
-
: "# PAL Context\n
|
|
116
|
+
: "# PAL Context\n";
|
|
118
117
|
|
|
119
|
-
const state = readSetupState();
|
|
120
|
-
const setupPrompt = state ? buildSetupPrompt(state) : null;
|
|
121
118
|
const id = identity();
|
|
122
119
|
|
|
123
120
|
return template
|
|
124
|
-
.replace("{{SETUP_PROMPT}}", setupPrompt ? `${setupPrompt}\n` : "")
|
|
125
121
|
.replaceAll("{{IDENTITY_NAME}}", id.ai.name)
|
|
126
122
|
.replaceAll("{{IDENTITY_DISPLAY}}", id.ai.displayName)
|
|
127
123
|
.replaceAll("{{IDENTITY_CATCHPHRASE}}", id.ai.catchphrase)
|
package/src/hooks/lib/context.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { paths } from "./paths";
|
|
|
13
13
|
import { loadRecentNotes } from "./relationship";
|
|
14
14
|
import { readSessionNames } from "./session-names";
|
|
15
15
|
import * as settings from "./settings";
|
|
16
|
-
import {
|
|
16
|
+
import { isSetupComplete, readSetupState, remainingSteps, STEP_ORDER } from "./setup";
|
|
17
17
|
import { computeSignalTrends, formatTrends } from "./signal-trends";
|
|
18
18
|
import { readFramePrinciples } from "./wisdom";
|
|
19
19
|
import { readProjectHistory, readSessions, recentSessions } from "./work-tracking";
|
|
@@ -141,12 +141,12 @@ export function buildGreeting(): string[] {
|
|
|
141
141
|
const counts = loadCachedCounts();
|
|
142
142
|
const work = loadActiveWork();
|
|
143
143
|
const setupState = readSetupState();
|
|
144
|
-
const
|
|
144
|
+
const setupIncomplete = setupState && !isSetupComplete(setupState);
|
|
145
145
|
|
|
146
146
|
const greeting: string[] = [];
|
|
147
147
|
|
|
148
|
-
if (
|
|
149
|
-
const done = STEP_ORDER.length -
|
|
148
|
+
if (setupIncomplete) {
|
|
149
|
+
const done = STEP_ORDER.length - remainingSteps(setupState).length;
|
|
150
150
|
greeting.push(
|
|
151
151
|
`🔧 PAL setup ${done}/${STEP_ORDER.length} | ${counts.signals} signals`
|
|
152
152
|
);
|
package/src/hooks/lib/setup.ts
CHANGED
|
@@ -23,31 +23,33 @@ export interface SetupState {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** Ordered setup steps — defines the wizard flow */
|
|
26
|
-
const SETUP_STEPS: Record<string, Omit<SetupStep, "done">> = {
|
|
26
|
+
export const SETUP_STEPS: Record<string, Omit<SetupStep, "done">> = {
|
|
27
27
|
mission: {
|
|
28
28
|
file: "telos/MISSION.md",
|
|
29
|
-
question:
|
|
30
|
-
|
|
29
|
+
question:
|
|
30
|
+
"What do you do? What's your role and core purpose? (~/.pal/telos/MISSION.md)",
|
|
31
|
+
hint: "e.g. Senior software engineer building developer tooling at Acme Corp",
|
|
31
32
|
},
|
|
32
33
|
goals: {
|
|
33
34
|
file: "telos/GOALS.md",
|
|
34
|
-
question:
|
|
35
|
-
|
|
35
|
+
question:
|
|
36
|
+
"What are your current goals? (short-term, medium-term, long-term) (~/.pal/telos/GOALS.md)",
|
|
37
|
+
hint: "e.g. Ship v2 by Q3, learn Rust, get promoted to staff engineer",
|
|
36
38
|
},
|
|
37
39
|
projects: {
|
|
38
40
|
file: "telos/PROJECTS.md",
|
|
39
|
-
question: "What projects are you currently working on?",
|
|
40
|
-
hint: "
|
|
41
|
+
question: "What projects are you currently working on? (~/.pal/telos/PROJECTS.md)",
|
|
42
|
+
hint: "e.g. PAL (active, high priority), personal blog (paused), side SaaS (early stage)",
|
|
41
43
|
},
|
|
42
44
|
beliefs: {
|
|
43
45
|
file: "telos/BELIEFS.md",
|
|
44
|
-
question: "What principles or values guide your work?",
|
|
45
|
-
hint: "
|
|
46
|
+
question: "What principles or values guide your work? (~/.pal/telos/BELIEFS.md)",
|
|
47
|
+
hint: "e.g. Simple code > clever code, ship early and iterate, always write tests",
|
|
46
48
|
},
|
|
47
49
|
challenges: {
|
|
48
50
|
file: "telos/CHALLENGES.md",
|
|
49
|
-
question: "What are your biggest current challenges?",
|
|
50
|
-
hint: "
|
|
51
|
+
question: "What are your biggest current challenges? (~/.pal/telos/CHALLENGES.md)",
|
|
52
|
+
hint: "e.g. Context switching between projects, unclear requirements, work-life balance",
|
|
51
53
|
},
|
|
52
54
|
};
|
|
53
55
|
|
|
@@ -58,21 +60,17 @@ function setupPath(): string {
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/** Check if a TELOS file has real content (not just template scaffolding) */
|
|
61
|
-
function hasRealContent(filePath: string): boolean {
|
|
63
|
+
export function hasRealContent(filePath: string): boolean {
|
|
62
64
|
if (!existsSync(filePath)) return false;
|
|
63
65
|
try {
|
|
64
66
|
const content = readFileSync(filePath, "utf-8").trim();
|
|
65
|
-
return content
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
l.trim() &&
|
|
73
|
-
!/^\s*-\s*$/.test(l) &&
|
|
74
|
-
!/^\s*\|/.test(l)
|
|
75
|
-
);
|
|
67
|
+
return content.split("\n").some((l) => {
|
|
68
|
+
if (!l.trim()) return false;
|
|
69
|
+
if (l.startsWith("#")) return false;
|
|
70
|
+
if (l.startsWith("<!--") || l.startsWith("-->")) return false;
|
|
71
|
+
if (/^\s*-\s*$/.test(l)) return false;
|
|
72
|
+
return true; // includes table rows (| ... |) — counts as real content
|
|
73
|
+
});
|
|
76
74
|
} catch {
|
|
77
75
|
return false;
|
|
78
76
|
}
|
|
@@ -123,55 +121,3 @@ export function remainingSteps(state: SetupState): string[] {
|
|
|
123
121
|
export function isSetupComplete(state: SetupState): boolean {
|
|
124
122
|
return state.completed;
|
|
125
123
|
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Build the system-prompt instructions for the current setup state.
|
|
129
|
-
* Returns null if setup is already complete.
|
|
130
|
-
*/
|
|
131
|
-
export function buildSetupPrompt(state: SetupState): string | null {
|
|
132
|
-
if (state.completed) return null;
|
|
133
|
-
|
|
134
|
-
const remaining = remainingSteps(state);
|
|
135
|
-
if (remaining.length === 0) return null;
|
|
136
|
-
|
|
137
|
-
const completedSteps = STEP_ORDER.filter((k) => state.steps[k]?.done);
|
|
138
|
-
const totalSteps = STEP_ORDER.length;
|
|
139
|
-
|
|
140
|
-
const lines: string[] = [
|
|
141
|
-
"## IMPORTANT: PAL First-Run Setup Required",
|
|
142
|
-
"",
|
|
143
|
-
"TELOS files are empty — the user's identity is already configured (via the installer),",
|
|
144
|
-
"but personal context is still needed. You MUST start the setup process immediately.",
|
|
145
|
-
"Greet them, explain that PAL needs to learn about them to personalize future sessions,",
|
|
146
|
-
"and ask the first remaining question below. Do NOT wait for the user to ask about setup.",
|
|
147
|
-
"",
|
|
148
|
-
];
|
|
149
|
-
|
|
150
|
-
if (completedSteps.length > 0) {
|
|
151
|
-
lines.push(
|
|
152
|
-
`Setup in progress — ${completedSteps.length}/${totalSteps} steps complete. Continue from the next remaining step.`,
|
|
153
|
-
""
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
lines.push("### Steps to complete (ask one at a time):", "");
|
|
158
|
-
|
|
159
|
-
for (const key of remaining) {
|
|
160
|
-
const step = state.steps[key];
|
|
161
|
-
lines.push(`- **${key}** — Ask: "${step.question}" → ${step.hint}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
lines.push(
|
|
165
|
-
"",
|
|
166
|
-
"### After each answer:",
|
|
167
|
-
"1. Write the user's answer to the corresponding TELOS file.",
|
|
168
|
-
`2. Read \`memory/state/setup.json\`, set \`steps.<key>.done = true\`, and write it back.`,
|
|
169
|
-
"3. Ask the next remaining question.",
|
|
170
|
-
"",
|
|
171
|
-
`When all steps are done (or the user wants to skip), set \`completed: true\` in setup.json.`,
|
|
172
|
-
"",
|
|
173
|
-
"Keep it conversational and natural. If the user wants to skip a step, mark it done and move on."
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
return lines.join("\n");
|
|
177
|
-
}
|
package/src/tools/self-model.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { parseArgs } from "node:util";
|
|
|
21
21
|
import { inference } from "../hooks/lib/inference";
|
|
22
22
|
import { SONNET_MODEL } from "../hooks/lib/models";
|
|
23
23
|
import { ensureDir, paths } from "../hooks/lib/paths";
|
|
24
|
+
import { logTokenUsage } from "../hooks/lib/token-usage";
|
|
24
25
|
|
|
25
26
|
// ── Config ──
|
|
26
27
|
|
|
@@ -537,6 +538,8 @@ export async function composeSelfModel(days: number): Promise<string> {
|
|
|
537
538
|
timeout: 30000,
|
|
538
539
|
});
|
|
539
540
|
|
|
541
|
+
if (result.usage) logTokenUsage("self-model", result.usage, SONNET_MODEL);
|
|
542
|
+
|
|
540
543
|
if (result.success && result.output) {
|
|
541
544
|
// Append meta line if inference didn't include it
|
|
542
545
|
const output = result.output;
|
package/src/tools/token-cost.ts
CHANGED
|
@@ -258,10 +258,11 @@ export function readClaudeCode(projectFilter?: string): {
|
|
|
258
258
|
return { buckets, byModel, byProject };
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
// ── PAL
|
|
261
|
+
// ── PAL inference ──
|
|
262
262
|
|
|
263
263
|
export function readPalInference(): {
|
|
264
264
|
buckets: TimeBuckets;
|
|
265
|
+
byModel: Record<string, TimeBuckets>;
|
|
265
266
|
byCaller: Record<string, Bucket>;
|
|
266
267
|
} {
|
|
267
268
|
const now = new Date();
|
|
@@ -270,13 +271,14 @@ export function readPalInference(): {
|
|
|
270
271
|
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
271
272
|
|
|
272
273
|
const buckets = emptyTimeBuckets();
|
|
274
|
+
const byModel: Record<string, TimeBuckets> = {};
|
|
273
275
|
const byCaller: Record<string, Bucket> = {};
|
|
274
276
|
|
|
275
277
|
const filepath = resolve(palHome(), "memory", "signals", "token-usage.jsonl");
|
|
276
|
-
if (!existsSync(filepath)) return { buckets, byCaller };
|
|
278
|
+
if (!existsSync(filepath)) return { buckets, byModel, byCaller };
|
|
277
279
|
|
|
278
280
|
const content = readFileSync(filepath, "utf-8").trim();
|
|
279
|
-
if (!content) return { buckets, byCaller };
|
|
281
|
+
if (!content) return { buckets, byModel, byCaller };
|
|
280
282
|
|
|
281
283
|
for (const line of content.split("\n")) {
|
|
282
284
|
try {
|
|
@@ -299,6 +301,19 @@ export function readPalInference(): {
|
|
|
299
301
|
weekAgo,
|
|
300
302
|
monthAgo
|
|
301
303
|
);
|
|
304
|
+
if (!byModel[e.model]) byModel[e.model] = emptyTimeBuckets();
|
|
305
|
+
addToTimeBuckets(
|
|
306
|
+
byModel[e.model],
|
|
307
|
+
e.ts,
|
|
308
|
+
e.model,
|
|
309
|
+
e.inputTokens,
|
|
310
|
+
e.outputTokens,
|
|
311
|
+
0,
|
|
312
|
+
0,
|
|
313
|
+
todayPrefix,
|
|
314
|
+
weekAgo,
|
|
315
|
+
monthAgo
|
|
316
|
+
);
|
|
302
317
|
if (!byCaller[e.caller]) byCaller[e.caller] = emptyBucket();
|
|
303
318
|
addToBucket(byCaller[e.caller], e.model, e.inputTokens, e.outputTokens, 0, 0);
|
|
304
319
|
} catch {
|
|
@@ -306,7 +321,7 @@ export function readPalInference(): {
|
|
|
306
321
|
}
|
|
307
322
|
}
|
|
308
323
|
|
|
309
|
-
return { buckets, byCaller };
|
|
324
|
+
return { buckets, byModel, byCaller };
|
|
310
325
|
}
|
|
311
326
|
|
|
312
327
|
// ── CLI ──
|
|
@@ -352,12 +367,18 @@ function run() {
|
|
|
352
367
|
}
|
|
353
368
|
}
|
|
354
369
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
370
|
+
for (const [model, tb] of Object.entries(pal.byModel)) {
|
|
371
|
+
if (tb.total.calls === 0) continue;
|
|
372
|
+
const label = model.includes("haiku")
|
|
373
|
+
? "Haiku"
|
|
374
|
+
: model.includes("sonnet")
|
|
375
|
+
? "Sonnet"
|
|
376
|
+
: model.replace("claude-", "");
|
|
377
|
+
console.log(`\n PAL Inference (${label})\n`);
|
|
378
|
+
printRow("Today", tb.today);
|
|
379
|
+
printRow("7d", tb.week);
|
|
380
|
+
printRow("30d", tb.month);
|
|
381
|
+
printRow("Total", tb.total);
|
|
361
382
|
}
|
|
362
383
|
|
|
363
384
|
const grand = emptyBucket();
|