portable-agent-layer 0.70.0 → 0.71.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/README.md +5 -1
- package/assets/schema/pal-settings.schema.json +4 -0
- package/assets/skills/onboarding/SKILL.md +109 -0
- package/assets/skills/projects/SKILL.md +11 -2
- package/assets/templates/pal-settings.json +1 -0
- package/package.json +5 -1
- package/src/cli/index.ts +39 -12
- package/src/cli/personal-context.ts +67 -0
- package/src/cli/server.ts +13 -7
- package/src/cli/setup-identity.ts +13 -1
- package/src/hooks/handlers/agenda.ts +223 -0
- package/src/hooks/handlers/inject-retrieval.ts +6 -2
- package/src/hooks/lib/agenda-store.ts +41 -0
- package/src/hooks/lib/paths.ts +0 -1
- package/src/hooks/lib/projects.ts +16 -1
- package/src/hooks/lib/serves.ts +60 -0
- package/src/hooks/lib/stop.ts +14 -0
- package/src/hooks/lib/telos-goals.ts +144 -0
- package/src/hooks/lib/telos-topics.ts +68 -0
- package/src/hooks/lib/token-usage.ts +3 -1
- package/src/hooks/lib/wall-clock.ts +58 -0
- package/src/tools/agent/handoff-note.ts +38 -20
- package/src/tools/agent/project.ts +36 -4
- package/src/tools/control-room/data.ts +332 -0
- package/src/tools/control-room/matrix.ts +182 -0
- package/src/tools/control-room/server.ts +150 -0
- package/src/tools/control-room/ui/agenda.tsx +43 -0
- package/src/tools/control-room/ui/agents.tsx +67 -0
- package/src/tools/control-room/ui/app.css +857 -0
- package/src/tools/control-room/ui/app.tsx +74 -0
- package/src/tools/control-room/ui/board.tsx +82 -0
- package/src/tools/control-room/ui/format.ts +31 -0
- package/src/tools/control-room/ui/handoffs.tsx +37 -0
- package/src/tools/control-room/ui/index.html +19 -0
- package/src/tools/control-room/ui/ledger.tsx +136 -0
- package/src/tools/control-room/ui/matrix.tsx +117 -0
- package/src/tools/control-room/ui/panel.tsx +60 -0
- package/src/tools/control-room/ui/signal.tsx +161 -0
- package/assets/templates/ledger-page.html +0 -213
- package/src/cli/setup-telos.ts +0 -52
- package/src/hooks/lib/setup.ts +0 -60
- package/src/tools/ledger/server.ts +0 -111
package/README.md
CHANGED
|
@@ -87,9 +87,11 @@ pal cli status # check your setup
|
|
|
87
87
|
| `pal cli usage` | Summarize token usage and estimated cost |
|
|
88
88
|
| `pal cli actor [label <name>]` | Show or rename the actor — who caused a record. Travels with an export, so a shared memory can tell two people apart |
|
|
89
89
|
| `pal cli machine [label <name>]` | Show or rename this install — where a record was written. Never leaves the machine |
|
|
90
|
+
| `pal cli telos` | Which TELOS topics are answered, in interview order, and which one comes next |
|
|
91
|
+
| `pal cli timezone [<zone>]` | Show or set your timezone. IANA names only, validated before it is stored |
|
|
90
92
|
| `pal cli knowledge` | Query & manage the knowledge store (search, graph, stats, hubs, find, show, add, ls, ingest) |
|
|
91
93
|
| `pal cli ledger` | Query the action ledger — `log`, `show <id>`, `stats`, filtered by `--project`, `--since`, `--actor`, `--machine`, `--runtime`, `--outcome`, `--tool`, `--target` |
|
|
92
|
-
| `pal cli server` |
|
|
94
|
+
| `pal cli server` | The morning screen: a local page to open before a terminal — three moves for today, an urgent/important grid over every project *and* every stated goal, where you left off, and a drawer holding the signal and the action ledger — `start [--port <n>]`, `stop`, `status`. Loopback only, default port 7250. No model runs on page load |
|
|
93
95
|
| `pal cli skill link <name>` | Link a personal `~/.pal/skills/<name>/` into every installed agent so it is discoverable |
|
|
94
96
|
| `pal cli skill doctor <name>` | Evaluate a skill against the authoring best practices (folder/file-name match, name, description, body length, point-of-view, reference depth) |
|
|
95
97
|
| `pal cli subagent link <name>` | Install a personal `~/.pal/agents/<name>.md` (merged multi-platform definition) into every installed agent, split per platform |
|
|
@@ -185,6 +187,7 @@ PAL ships with built-in skills that extend your agent's capabilities:
|
|
|
185
187
|
| `frontend-design` | Build production-grade frontend interfaces |
|
|
186
188
|
| `fyzz-chat-api` | Query Fyzz Chat conversations via API |
|
|
187
189
|
| `humanize` | Rewrite text to strip AI tells and read as human |
|
|
190
|
+
| `onboarding` | Interview the user to fill empty TELOS topics and the timezone, one topic at a time |
|
|
188
191
|
| `opinion` | Confirm or contradict tracked opinions (confidence-weighted) |
|
|
189
192
|
| `pal-analyze` | Surface rating trends, failure patterns, and graduation candidates |
|
|
190
193
|
| `pal-reflect` | Promote recurring observations into tracked opinions |
|
|
@@ -223,6 +226,7 @@ Your setup should be able to travel with you.
|
|
|
223
226
|
- **Cross-agent**: full support for Claude Code, opencode, Cursor, GitHub Copilot, and Codex (Codex still lacks subagents)
|
|
224
227
|
- **Subscription-first inference**: background inference routes through whichever subscription CLI is active — no API key needed by default
|
|
225
228
|
- **Portable knowledge**: export and import accumulated knowledge
|
|
229
|
+
- **A morning screen, not a dashboard**: `pal cli server` ranks your projects *and* your written goals in one urgent/important grid. Urgency is read off the files — blockers, an unfinished handoff, a date in a next step, an important thing gone quiet. Importance comes from one fact per project: whether it serves a goal you wrote down, is a way the work could pay, or is kept for its own sake. PAL guesses that once at session stop; you correct it on the page or with `project.ts serves <name> goal|revenue|fun`, and the correction survives every later guess
|
|
226
230
|
- **TypeScript-first**: built in TypeScript from day one
|
|
227
231
|
- **Open source**: hackable, inspectable, extensible
|
|
228
232
|
- **Composable**: intended to fit into real developer workflows
|
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
"type": "boolean",
|
|
70
70
|
"description": "Prompt-time \"Potential matching skills\" hints, matched from each skill's metadata.triggers."
|
|
71
71
|
},
|
|
72
|
+
"wallClock": {
|
|
73
|
+
"type": "boolean",
|
|
74
|
+
"description": "One line of the current time with every prompt, in identity.principal.timezone, so a session resumed hours later knows the day changed."
|
|
75
|
+
},
|
|
72
76
|
"steeringTestReport": { "type": "boolean", "description": "When true, the assistant notes to the user which steering self-check fired. For the dual-live test period; ships false." }
|
|
73
77
|
}
|
|
74
78
|
},
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: onboarding
|
|
3
|
+
license: MIT
|
|
4
|
+
description: "Interview the user to fill in the personal context PAL has none of: their TELOS topics, one per pass, and their timezone. Use when the user wants to set up or flesh out that context: onboard me, fill in my telos, interview me about my goals, PAL doesn't know anything about me, help me write my mission, what should PAL know about me."
|
|
5
|
+
argument-hint: "[topic to start with]"
|
|
6
|
+
metadata:
|
|
7
|
+
source: portable-agent-layer
|
|
8
|
+
triggers:
|
|
9
|
+
- "onboarding"
|
|
10
|
+
- "onboard me"
|
|
11
|
+
- "fill in my telos"
|
|
12
|
+
- "set up my telos"
|
|
13
|
+
- "interview me about my goals"
|
|
14
|
+
- "personal context is empty"
|
|
15
|
+
- "you don't know anything about me"
|
|
16
|
+
- "help me write my mission"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
Fill the personal context by asking, one topic per pass, and write only what the user actually said.
|
|
20
|
+
|
|
21
|
+
TELOS is the context that orients every future session: purpose, goals, obstacles, methods, principles. The timezone is what makes "this morning" mean anything. A fresh install ships the TELOS files as empty scaffolds and guesses the timezone from the machine, and both stay that way until someone asks. This skill is that asking. It is deliberately not part of install: a user who has just installed a tool cannot yet say what they want from it, and answers given under that pressure are worse than no answers.
|
|
22
|
+
|
|
23
|
+
## Ask the CLI what is missing
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pal cli telos
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
It prints every topic as answered or unanswered, in interview order, and names the next unanswered one. That command owns the definition of "unanswered" — a scaffold of headings and empty bullets is not an answer. Do not open the files to judge for yourself, and do not restate the rule here: one definition, in one place, is the point.
|
|
30
|
+
|
|
31
|
+
Read a file only when you need the format of an existing entry, or when the user asks what is already on record.
|
|
32
|
+
|
|
33
|
+
## Workflow
|
|
34
|
+
|
|
35
|
+
1. Run `pal cli telos`.
|
|
36
|
+
|
|
37
|
+
2. Pick this pass's topic:
|
|
38
|
+
- The user named one in the argument → use it, answered or not.
|
|
39
|
+
- Otherwise → the `next:` topic the command printed.
|
|
40
|
+
- `next: none` → say so, list what is on file in one line each, and offer the optional topics. Do not re-interview an answered topic unless asked.
|
|
41
|
+
|
|
42
|
+
| File | The question behind it |
|
|
43
|
+
|------|------------------------|
|
|
44
|
+
| `MISSION.md` | What are you actually doing with your working life? |
|
|
45
|
+
| `GOALS.md` | What are you working toward, by when? |
|
|
46
|
+
| `CHALLENGES.md` | What is in the way right now? |
|
|
47
|
+
| `STRATEGIES.md` | How do you approach work and decisions? |
|
|
48
|
+
| `BELIEFS.md` | What principles do you refuse to trade away? |
|
|
49
|
+
| `MODELS.md` `NARRATIVES.md` `LEARNED.md` `IDEAS.md` | Optional, on request only |
|
|
50
|
+
|
|
51
|
+
3. Open the topic with one sentence saying why the answer changes how you behave in future sessions, then ask **one** question. Never ask two questions in one turn, and never present a numbered list of questions to work through.
|
|
52
|
+
|
|
53
|
+
4. Follow up until the answer is specific enough to act on. A usable answer names something concrete: a company, a role, a date, an amount, a named obstacle. A vague answer ("grow professionally", "be healthier") gets one follow-up asking for the concrete version. Stop after two follow-ups on the same point whether or not it got sharper.
|
|
54
|
+
|
|
55
|
+
5. Draft the entry in the file's existing format, show it, and ask for a yes before writing. The draft may only contain what the user said in this conversation. If they went quiet on part of it, that part is absent from the draft, not filled in by you.
|
|
56
|
+
|
|
57
|
+
6. Write it with the telos tool, never by editing the file:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bun ~/.pal/skills/telos/tools/update-telos.ts <FILE> "<content>" "<description>"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`<FILE>` is the bare filename, `<description>` is a short line naming what the entry covers and the date it was given.
|
|
64
|
+
|
|
65
|
+
7. Report what landed, name the next unanswered topic, and stop. One topic per invocation. The user resumes by invoking the skill again, whenever that is.
|
|
66
|
+
|
|
67
|
+
## The timezone
|
|
68
|
+
|
|
69
|
+
Check it on the first pass only:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pal cli timezone # show what is configured
|
|
73
|
+
pal cli timezone Europe/Budapest # set it, IANA names only
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Install prefills it from the machine, so it is usually already right and usually not worth a question. Ask only when it reads as not set, or when the user has corrected you about the date or the hour in this session. Settings are hook-protected, so this command is the only way to change it — never edit `pal-settings.json`.
|
|
77
|
+
|
|
78
|
+
## Never invent an answer
|
|
79
|
+
|
|
80
|
+
The whole value of this context is that it is the user's own words. An invented goal is worse than an empty file, because every later session treats it as fact and ranks work against it.
|
|
81
|
+
|
|
82
|
+
- Do not draft an answer from the repository, the user's projects, their git history, or anything you inferred earlier in the session.
|
|
83
|
+
- Do not offer a filled-in answer for confirmation. "Is your mission to build a small consultancy?" invites a yes that was yours, not theirs.
|
|
84
|
+
- You may offer two or three short examples of the *kind* of answer the question wants, clearly marked as examples from nobody in particular, when the user is stuck.
|
|
85
|
+
- "I don't know" is a valid answer. Say the topic stays open, write nothing, and move to the next one.
|
|
86
|
+
- If the user's answer contradicts what is already on file, show both and ask which holds. Do not merge them silently.
|
|
87
|
+
|
|
88
|
+
## Output format
|
|
89
|
+
|
|
90
|
+
Conversational, not a form. Per pass:
|
|
91
|
+
|
|
92
|
+
- One line on why this topic matters to future sessions.
|
|
93
|
+
- One question, then a real exchange.
|
|
94
|
+
- A drafted entry shown before writing, and an explicit request to confirm.
|
|
95
|
+
- After writing: one line confirming the file, and one line naming the next unanswered topic.
|
|
96
|
+
|
|
97
|
+
## When to use
|
|
98
|
+
|
|
99
|
+
- The user asks to be onboarded or interviewed, or to fill in, set up, or flesh out their personal context.
|
|
100
|
+
- The user says PAL knows nothing about them, or asks what it should know.
|
|
101
|
+
- A task needs personal context (ranking work, weighing a decision) and `pal cli telos` reports the relevant topic unanswered. Offer this skill; do not start the interview uninvited.
|
|
102
|
+
|
|
103
|
+
## Do NOT use
|
|
104
|
+
|
|
105
|
+
- To read, view, or summarize existing TELOS content, or to record a single stated change to it — that is the `telos` skill.
|
|
106
|
+
- To act on a timezone the user simply states ("we moved to Berlin") — run `pal cli timezone` and move on. No interview.
|
|
107
|
+
- To record an observation about the user made during other work — that is a relationship note.
|
|
108
|
+
- To set the assistant's name or the startup catchphrase — that is identity, and `pal cli init` asks for it.
|
|
109
|
+
- To fill several topics in one pass because the user seems willing. One topic per invocation is the design; long forms produce shallow answers.
|
|
@@ -38,7 +38,8 @@ Output is JSON.
|
|
|
38
38
|
| Command | Purpose |
|
|
39
39
|
|---------|---------|
|
|
40
40
|
| `list` | All registered projects with status, path, updated, stale flag, and counts |
|
|
41
|
-
| `create [name] [--path PATH] [--objectives "a;b;c"]` | Register a project. Defaults: name=basename(cwd), path=cwd. Slug must be `[a-z0-9_-]
|
|
41
|
+
| `create [name] [--path PATH] [--objectives "a;b;c"] [--serves KIND] [--serves-note "..."]` | Register a project. Defaults: name=basename(cwd), path=cwd. Slug must be `[a-z0-9_-]+`. KIND is `goal`, `revenue` or `fun` |
|
|
42
|
+
| `serves <name> <goal\|revenue\|fun> [note]` | Set what the project is for, after the fact |
|
|
42
43
|
| `resume <name>` | Print the full project ISA — all frontmatter and body sections |
|
|
43
44
|
| `add-next <name> "text"` | Append a next step (array, instantly appendable) |
|
|
44
45
|
| `add-blocker <name> "text"` | Append a blocker (array, instantly appendable) |
|
|
@@ -94,6 +95,7 @@ When SessionStart context flags the current cwd as unregistered (e.g. `💡 cwd
|
|
|
94
95
|
- **Default name** = the FULL last path segment of cwd, lowercased. For `/repos/portable-agent-layer` → `portable-agent-layer`. Never split on `-`.
|
|
95
96
|
- **Confirm before creating.** Never auto-create without explicit user approval ("yes", "do it", "register").
|
|
96
97
|
- **Capture context in conversation.** If the user accepts but doesn't volunteer a goal, ask one short question, or infer from the last few messages and confirm.
|
|
98
|
+
- **Ask what it serves.** One question, at registration: is this moving a stated goal forward, a way the work could pay, or kept for its own sake? Pass the answer as `--serves goal|revenue|fun` with a six-word `--serves-note`. It is the only thing that ranks the project against the others, and PAL will guess it later if you skip it — a guess the user then has to correct.
|
|
97
99
|
|
|
98
100
|
### When NOT to suggest registration
|
|
99
101
|
|
|
@@ -120,7 +122,14 @@ User: "store under <project> that a reference implementation exists in this repo
|
|
|
120
122
|
```
|
|
121
123
|
User: "track this project"
|
|
122
124
|
→ Default name from cwd basename, confirm with user
|
|
123
|
-
→
|
|
125
|
+
→ Ask what it serves: a goal, a way it could pay, or fun
|
|
126
|
+
→ bun ~/.pal/tools/project.ts create --path "$(pwd)" --objectives "first objective; second objective" --serves revenue --serves-note "could be sold as a service"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Correcting what a project is for**
|
|
130
|
+
```
|
|
131
|
+
User: "<project> isn't a toy, it's the thing I'd actually sell"
|
|
132
|
+
→ bun ~/.pal/tools/project.ts serves <slug> revenue "the one I would sell"
|
|
124
133
|
```
|
|
125
134
|
|
|
126
135
|
**Logging a decision**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portable-agent-layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -83,6 +83,8 @@
|
|
|
83
83
|
"@types/adm-zip": "^0.5.8",
|
|
84
84
|
"@types/bun": "latest",
|
|
85
85
|
"@types/node": "latest",
|
|
86
|
+
"@types/react": "^19.2.18",
|
|
87
|
+
"@types/react-dom": "^19.2.7",
|
|
86
88
|
"husky": "^9.1.7",
|
|
87
89
|
"jscpd": "^4.2.3",
|
|
88
90
|
"knip": "^6.14.1",
|
|
@@ -100,6 +102,8 @@
|
|
|
100
102
|
"marked": "18.0.4",
|
|
101
103
|
"pdf-lib": "1.17.1",
|
|
102
104
|
"playwright": "^1.60.0",
|
|
105
|
+
"react": "^19.2.8",
|
|
106
|
+
"react-dom": "^19.2.8",
|
|
103
107
|
"unpdf": "^0.12.0"
|
|
104
108
|
}
|
|
105
109
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* doctor Check prerequisites and system health
|
|
18
18
|
* usage Summarize token usage and cost
|
|
19
19
|
* ledger <sub> [filters] Query the action ledger (log · show · stats)
|
|
20
|
-
* server start|stop|status
|
|
20
|
+
* server start|stop|status The control room, a local page over ~/.pal
|
|
21
21
|
* skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
|
|
22
22
|
* skill doctor <name|--all> Evaluate one skill, or every installed skill, against the authoring best practices
|
|
23
23
|
* subagent link <name> Install a personal ~/.pal/agents/<name>.md into installed agents
|
|
@@ -48,7 +48,7 @@ import { DEBUG_LOG_MAX_ROTATED, logDebug } from "../hooks/lib/log";
|
|
|
48
48
|
import { ensureRegistered, writeRegistryEntry } from "../hooks/lib/machine";
|
|
49
49
|
import { palHome, palPkg, paths, platform } from "../hooks/lib/paths";
|
|
50
50
|
import { auditBindings, describeBindingIssue } from "../hooks/lib/projects";
|
|
51
|
-
import {
|
|
51
|
+
import { telosStatus } from "../hooks/lib/telos-topics";
|
|
52
52
|
import { log } from "../targets/lib";
|
|
53
53
|
import { checkPendingMigrations } from "./migrate";
|
|
54
54
|
|
|
@@ -265,6 +265,18 @@ async function runCli(command: string | undefined, args: string[]) {
|
|
|
265
265
|
if (code !== 0) process.exit(code);
|
|
266
266
|
break;
|
|
267
267
|
}
|
|
268
|
+
case "telos": {
|
|
269
|
+
const { runTelos } = await import("./personal-context");
|
|
270
|
+
const code = runTelos(args);
|
|
271
|
+
if (code !== 0) process.exit(code);
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case "timezone": {
|
|
275
|
+
const { runTimezone } = await import("./personal-context");
|
|
276
|
+
const code = runTimezone(args);
|
|
277
|
+
if (code !== 0) process.exit(code);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
268
280
|
case "debug":
|
|
269
281
|
cliDebug(args);
|
|
270
282
|
break;
|
|
@@ -291,6 +303,19 @@ function banner() {
|
|
|
291
303
|
console.log("");
|
|
292
304
|
}
|
|
293
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Install asks for identity and stops. TELOS is a conversation, not a form: a
|
|
308
|
+
* user who has just installed a tool cannot yet say what they want from it, and
|
|
309
|
+
* answers given under that pressure are worse than none.
|
|
310
|
+
*/
|
|
311
|
+
function pointAtOnboarding(): void {
|
|
312
|
+
const unanswered = telosStatus().filter((topic) => topic.priority && !topic.answered);
|
|
313
|
+
if (unanswered.length === 0) return;
|
|
314
|
+
log.info(
|
|
315
|
+
`PAL knows nothing about you yet (${unanswered.length} topics open). When you have half an hour, ask your agent to onboard you.`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
294
319
|
function showHelp() {
|
|
295
320
|
console.log(`
|
|
296
321
|
Usage:
|
|
@@ -311,11 +336,13 @@ function showHelp() {
|
|
|
311
336
|
pal cli usage Summarize token usage and cost
|
|
312
337
|
pal cli actor [label <name>] Show or rename this actor (who caused a record)
|
|
313
338
|
pal cli machine [label <name>] Show or rename this install (where it was written)
|
|
339
|
+
pal cli telos Which TELOS topics are answered, in interview order
|
|
340
|
+
pal cli timezone [<zone>] Show or set your timezone (IANA name)
|
|
314
341
|
pal cli knowledge <sub> [args] Query & manage the knowledge store
|
|
315
342
|
(search · graph · stats · hubs · find · show · add · ls)
|
|
316
343
|
pal cli ledger <sub> [filters] Query the action ledger (log · show · stats)
|
|
317
344
|
e.g. ledger log --project X --since 7d
|
|
318
|
-
pal cli server start|stop|status
|
|
345
|
+
pal cli server start|stop|status The control room: a local page to open before a terminal
|
|
319
346
|
pal cli skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
|
|
320
347
|
pal cli skill doctor <name|--all> Evaluate one skill, or every installed skill
|
|
321
348
|
pal cli skill author-model Print the flagship model that authors skills for the active agent
|
|
@@ -871,15 +898,16 @@ function doctor(silent = false): DoctorResult {
|
|
|
871
898
|
: fail("CLAUDE.md — missing (run 'pal cli install --claude')");
|
|
872
899
|
}
|
|
873
900
|
|
|
874
|
-
//
|
|
901
|
+
// An empty TELOS is a normal state, not a broken install: the onboarding
|
|
902
|
+
// skill fills it whenever the user is ready, which may be months from now.
|
|
875
903
|
{
|
|
876
|
-
const
|
|
877
|
-
(
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
? ok("TELOS
|
|
904
|
+
const unanswered = telosStatus(home)
|
|
905
|
+
.filter((topic) => topic.priority && !topic.answered)
|
|
906
|
+
.map((topic) => topic.key);
|
|
907
|
+
unanswered.length === 0
|
|
908
|
+
? ok("TELOS answered")
|
|
881
909
|
: warn(
|
|
882
|
-
`TELOS
|
|
910
|
+
`TELOS unanswered — ${unanswered.join(", ")} (ask your agent to onboard you)`
|
|
883
911
|
);
|
|
884
912
|
}
|
|
885
913
|
|
|
@@ -1201,13 +1229,12 @@ async function install(targets: Targets) {
|
|
|
1201
1229
|
const { scaffoldTelos, scaffoldPalSettings, copyPalDocs, generateSkillIndex } =
|
|
1202
1230
|
await import("../targets/lib");
|
|
1203
1231
|
const { promptIdentity } = await import("./setup-identity");
|
|
1204
|
-
const { promptTelos } = await import("./setup-telos");
|
|
1205
1232
|
const { promptAttribution } = await import("./setup-attribution");
|
|
1206
1233
|
scaffoldTelos();
|
|
1207
1234
|
scaffoldPalSettings();
|
|
1208
1235
|
await promptIdentity();
|
|
1209
|
-
await promptTelos();
|
|
1210
1236
|
await promptAttribution();
|
|
1237
|
+
pointAtOnboarding();
|
|
1211
1238
|
|
|
1212
1239
|
// Registers the label loadActor derives, so it travels on the next export.
|
|
1213
1240
|
const { ensureActorRegistered } = await import("../hooks/lib/actor");
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pal cli telos / pal cli timezone — the personal context PAL carries about you.
|
|
3
|
+
*
|
|
4
|
+
* pal cli telos Which TELOS topics are answered, in interview order
|
|
5
|
+
* pal cli timezone Show the configured timezone
|
|
6
|
+
* pal cli timezone <zone> Set it, if Intl recognises the name
|
|
7
|
+
*
|
|
8
|
+
* Both exist for the onboarding skill. pal-settings.json is hook-protected, so
|
|
9
|
+
* an agent cannot write the timezone itself, and a skill that restated the
|
|
10
|
+
* answered/unanswered rule in prose would drift from the code that decides it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
identity,
|
|
15
|
+
raw as readSettings,
|
|
16
|
+
write as writeSettings,
|
|
17
|
+
} from "../hooks/lib/settings";
|
|
18
|
+
import { telosStatus } from "../hooks/lib/telos-topics";
|
|
19
|
+
import { canonicalTimeZone } from "../hooks/lib/wall-clock";
|
|
20
|
+
import { log } from "../targets/lib";
|
|
21
|
+
|
|
22
|
+
export function runTelos(args: string[]): number {
|
|
23
|
+
if (args.length > 0) {
|
|
24
|
+
log.error(`Unknown telos argument: ${args[0]}`);
|
|
25
|
+
log.info("Usage: pal cli telos");
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const topics = telosStatus();
|
|
30
|
+
for (const topic of topics) {
|
|
31
|
+
const mark = topic.answered ? "answered " : "unanswered";
|
|
32
|
+
const tier = topic.priority ? "" : " (optional)";
|
|
33
|
+
console.log(` ${mark} ${topic.key.padEnd(11)} ${topic.file}${tier}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const next = topics.find((topic) => topic.priority && !topic.answered);
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log(next ? `next: ${next.key}` : "next: none — every priority topic answered");
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function runTimezone(args: string[]): number {
|
|
43
|
+
const requested = args.join(" ").trim();
|
|
44
|
+
|
|
45
|
+
if (!requested) {
|
|
46
|
+
const current = identity().principal.timezone;
|
|
47
|
+
console.log(
|
|
48
|
+
current ? `timezone: ${current}` : "timezone: not set — times read as UTC"
|
|
49
|
+
);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const zone = canonicalTimeZone(requested);
|
|
54
|
+
if (!zone) {
|
|
55
|
+
log.error(`Not a timezone Intl recognises: ${requested}`);
|
|
56
|
+
log.info("Use an IANA name — Europe/Budapest, America/New_York, Asia/Tokyo");
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const settings = { ...readSettings() };
|
|
61
|
+
settings.identity ??= {};
|
|
62
|
+
settings.identity.principal ??= {};
|
|
63
|
+
settings.identity.principal.timezone = zone;
|
|
64
|
+
writeSettings(settings);
|
|
65
|
+
log.success(`timezone: ${zone}`);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
package/src/cli/server.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* pal cli server — start, stop and inspect the local
|
|
2
|
+
* pal cli server — start, stop and inspect the local control room.
|
|
3
3
|
*
|
|
4
|
-
* The server itself is src/tools/
|
|
4
|
+
* The server itself is src/tools/control-room/server.ts, run detached so it
|
|
5
5
|
* outlives the shell that started it. This file only owns the lifecycle:
|
|
6
6
|
* spawning, waiting for it to answer, remembering its pid, and killing it.
|
|
7
7
|
*/
|
|
@@ -11,7 +11,7 @@ import { resolve } from "node:path";
|
|
|
11
11
|
import { parseArgs } from "node:util";
|
|
12
12
|
import { spawnDetachedInference } from "../hooks/lib/detached-inference";
|
|
13
13
|
import { paths } from "../hooks/lib/paths";
|
|
14
|
-
import { DEFAULT_PORT, LOOPBACK, type ServerStatus } from "../tools/
|
|
14
|
+
import { DEFAULT_PORT, LOOPBACK, type ServerStatus } from "../tools/control-room/server";
|
|
15
15
|
|
|
16
16
|
interface ServerState {
|
|
17
17
|
pid: number;
|
|
@@ -19,7 +19,13 @@ interface ServerState {
|
|
|
19
19
|
startedAt: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const SERVER_SCRIPT = resolve(
|
|
22
|
+
const SERVER_SCRIPT = resolve(
|
|
23
|
+
import.meta.dir,
|
|
24
|
+
"..",
|
|
25
|
+
"tools",
|
|
26
|
+
"control-room",
|
|
27
|
+
"server.ts"
|
|
28
|
+
);
|
|
23
29
|
const STARTUP_TIMEOUT_MS = 3000;
|
|
24
30
|
const PROBE_TIMEOUT_MS = 500;
|
|
25
31
|
|
|
@@ -51,7 +57,7 @@ function showHelp(): void {
|
|
|
51
57
|
pal cli server <subcommand>
|
|
52
58
|
|
|
53
59
|
Subcommands:
|
|
54
|
-
start [--port <n>] Start the
|
|
60
|
+
start [--port <n>] Start the control room in the background (default port ${DEFAULT_PORT})
|
|
55
61
|
stop Stop it
|
|
56
62
|
status Show whether it is running, and where
|
|
57
63
|
|
|
@@ -131,11 +137,11 @@ async function cmdStart(args: string[]): Promise<number> {
|
|
|
131
137
|
return 0;
|
|
132
138
|
}
|
|
133
139
|
|
|
134
|
-
spawnDetachedInference(SERVER_SCRIPT, [`--port=${port}`], "
|
|
140
|
+
spawnDetachedInference(SERVER_SCRIPT, [`--port=${port}`], "control-room");
|
|
135
141
|
const status = await waitUntilAnswering(port);
|
|
136
142
|
if (!status)
|
|
137
143
|
return fail(
|
|
138
|
-
`The
|
|
144
|
+
`The control room did not answer on port ${port} within ${STARTUP_TIMEOUT_MS / 1000}s. Is the port free?`
|
|
139
145
|
);
|
|
140
146
|
|
|
141
147
|
writeState({ pid: status.pid, port, startedAt: status.startedAt });
|
|
@@ -9,6 +9,17 @@ import {
|
|
|
9
9
|
raw as readSettings,
|
|
10
10
|
write as writeSettings,
|
|
11
11
|
} from "../hooks/lib/settings";
|
|
12
|
+
import { isValidTimeZone } from "../hooks/lib/wall-clock";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Empty means the machine's guess, which clack substitutes after validation runs,
|
|
16
|
+
* so pressing Enter has to pass.
|
|
17
|
+
* @lintignore exercised directly by test/setup-identity.test.ts
|
|
18
|
+
*/
|
|
19
|
+
export function timezoneProblem(input: string | undefined): string | undefined {
|
|
20
|
+
if (!input || isValidTimeZone(input)) return undefined;
|
|
21
|
+
return `Not a timezone Intl recognises: ${input}`;
|
|
22
|
+
}
|
|
12
23
|
|
|
13
24
|
/** Prompt for missing identity fields. Skips any field that already has a value. */
|
|
14
25
|
export async function promptIdentity(): Promise<void> {
|
|
@@ -80,8 +91,9 @@ export async function promptIdentity(): Promise<void> {
|
|
|
80
91
|
if (needsTimezone) {
|
|
81
92
|
const guess = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
82
93
|
const tz = await clack.text({
|
|
83
|
-
message:
|
|
94
|
+
message: `Your timezone (Enter keeps ${guess})`,
|
|
84
95
|
defaultValue: guess,
|
|
96
|
+
validate: timezoneProblem,
|
|
85
97
|
});
|
|
86
98
|
if (clack.isCancel(tz)) {
|
|
87
99
|
clack.cancel("Setup cancelled");
|