@piagent/platform 1.0.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/.gitattributes +7 -0
- package/AGENTS.md +31 -0
- package/CHANGELOG.md +296 -0
- package/CODE_OF_CONDUCT.md +36 -0
- package/CONTRIBUTING.md +66 -0
- package/LICENSE +21 -0
- package/README.md +588 -0
- package/SECURITY.md +38 -0
- package/adapters/backend-api/profile.json +88 -0
- package/adapters/be-readonly-fe/profile.json +120 -0
- package/adapters/data/profile.json +88 -0
- package/adapters/devops/profile.json +92 -0
- package/adapters/docs/profile.json +74 -0
- package/adapters/fullstack/profile.json +94 -0
- package/adapters/generic/profile.json +95 -0
- package/adapters/mobile/profile.json +87 -0
- package/adapters/node-typescript/profile.json +76 -0
- package/adapters/python/profile.json +75 -0
- package/adapters/web-frontend/profile.json +101 -0
- package/catalog/capabilities.json +206 -0
- package/docs/architecture.md +64 -0
- package/docs/auto-delegation-policy.md +192 -0
- package/docs/capability-packs.md +234 -0
- package/docs/command-reference-vietnamese.md +484 -0
- package/docs/context-window-policy.md +85 -0
- package/docs/distribution-standard.md +229 -0
- package/docs/herdr-workflow.md +58 -0
- package/docs/mcp-and-tools.md +316 -0
- package/docs/memory-policy.md +164 -0
- package/docs/model-options.md +165 -0
- package/docs/oauth-providers.md +53 -0
- package/docs/operator-manual-vietnamese.md +1080 -0
- package/docs/package-architecture-notes.md +101 -0
- package/docs/project-adapters.md +174 -0
- package/docs/project-onboarding.md +135 -0
- package/docs/publishing-for-teams.md +64 -0
- package/docs/quality-benchmark.md +74 -0
- package/docs/quickstart-vietnamese.md +350 -0
- package/docs/release-install-policy.md +223 -0
- package/docs/runtime-harness-standard.md +118 -0
- package/docs/runtime-policy-design.md +132 -0
- package/docs/runtime-quality-baseline.md +257 -0
- package/docs/security-redaction-benchmark.md +56 -0
- package/docs/security-threat-model.md +66 -0
- package/docs/subagent-orchestration-capabilities.md +104 -0
- package/docs/subagents-and-multiagent.md +346 -0
- package/docs/task-implementation-contract.md +200 -0
- package/docs/team-onboarding.md +333 -0
- package/docs/usage-observability.md +130 -0
- package/docs/vercel-docs-site.md +115 -0
- package/docs/workflow-recipes.md +125 -0
- package/docs-site/assets/piagent-logo.svg +35 -0
- package/docs-site/favicon.svg +18 -0
- package/docs-site/index.html +1613 -0
- package/docs-site/vercel.json +24 -0
- package/evals/fixtures/action-proposal.invalid.json +34 -0
- package/evals/fixtures/action-proposal.valid.json +34 -0
- package/evals/fixtures/capability-pack.invalid.json +123 -0
- package/evals/fixtures/capability-pack.valid.json +123 -0
- package/evals/fixtures/capability-recipe.invalid.json +81 -0
- package/evals/fixtures/capability-recipe.valid.json +82 -0
- package/evals/fixtures/context-index.invalid.json +25 -0
- package/evals/fixtures/context-index.valid.json +25 -0
- package/evals/fixtures/eval-scenario.invalid.json +40 -0
- package/evals/fixtures/eval-scenario.valid.json +45 -0
- package/evals/fixtures/project-profile.invalid.json +88 -0
- package/evals/fixtures/project-profile.valid.json +95 -0
- package/evals/fixtures/task-contract.invalid.json +73 -0
- package/evals/fixtures/task-contract.valid.json +74 -0
- package/evals/golden/enforcement-decisions.json +329 -0
- package/evals/scenarios/capability-resolution.json +45 -0
- package/package.json +132 -0
- package/packages/piagent-core/README.md +147 -0
- package/packages/piagent-core/capabilities/capability-core.js +1023 -0
- package/packages/piagent-core/capabilities/capability-sources.js +265 -0
- package/packages/piagent-core/extensions/guard-io.js +45 -0
- package/packages/piagent-core/extensions/guard-shell-analysis.ts +558 -0
- package/packages/piagent-core/extensions/guard-types.ts +406 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +5760 -0
- package/packages/piagent-core/extensions/policy-core.js +1070 -0
- package/packages/piagent-core/extensions/redaction-core.js +4 -0
- package/packages/piagent-core/extensions/runtime-evidence.js +215 -0
- package/packages/piagent-core/package.json +46 -0
- package/packages/piagent-core/policies/base-policy.json +249 -0
- package/packages/piagent-core/prompts/be-to-fe.md +82 -0
- package/packages/piagent-core/prompts/commit.md +27 -0
- package/packages/piagent-core/prompts/discuss.md +35 -0
- package/packages/piagent-core/prompts/memory-policy.md +52 -0
- package/packages/piagent-core/prompts/model-options.md +68 -0
- package/packages/piagent-core/prompts/onboard-project.md +147 -0
- package/packages/piagent-core/prompts/piagent-commands.md +68 -0
- package/packages/piagent-core/prompts/plan.md +35 -0
- package/packages/piagent-core/prompts/platform-improve.md +69 -0
- package/packages/piagent-core/prompts/pr.md +26 -0
- package/packages/piagent-core/prompts/review.md +38 -0
- package/packages/piagent-core/prompts/scout.md +50 -0
- package/packages/piagent-core/prompts/task.md +69 -0
- package/packages/piagent-core/security/sensitive-data.js +187 -0
- package/packages/piagent-core/skills/piagent-ops/SKILL.md +44 -0
- package/packages/piagent-core/skills/piagent-source-cache/SKILL.md +59 -0
- package/packages/piagent-core/skills/piagent-source-cache/checkout-source-repo.sh +144 -0
- package/packages/piagent-core/subagents/piagent-oracle.md +29 -0
- package/packages/piagent-core/subagents/piagent-planner.md +46 -0
- package/packages/piagent-core/subagents/piagent-reviewer.md +48 -0
- package/packages/piagent-core/subagents/piagent-scout.md +37 -0
- package/packages/piagent-core/subagents/piagent-worker.md +44 -0
- package/packs/engineering-base/pack.json +123 -0
- package/packs/engineering-base/recipes/bounded-change.json +82 -0
- package/packs/web-delivery/pack.json +72 -0
- package/packs/web-delivery/recipes/verified-web-change.json +57 -0
- package/schemas/action-proposal.schema.json +90 -0
- package/schemas/capability-pack.schema.json +190 -0
- package/schemas/capability-recipe.schema.json +72 -0
- package/schemas/context-index.schema.json +258 -0
- package/schemas/eval-scenario.schema.json +67 -0
- package/schemas/project-profile.schema.json +555 -0
- package/schemas/task-contract.schema.json +314 -0
- package/scripts/audit-runtime-host.sh +78 -0
- package/scripts/bootstrap-local.sh +25 -0
- package/scripts/capability-catalog.mjs +290 -0
- package/scripts/check-runtime-advisories.mjs +114 -0
- package/scripts/configure-mcp.sh +301 -0
- package/scripts/configure-model-scope.sh +143 -0
- package/scripts/configure-subagents.sh +241 -0
- package/scripts/import-agent-instructions.mjs +199 -0
- package/scripts/init-project.sh +369 -0
- package/scripts/install-global.sh +532 -0
- package/scripts/link-project.sh +25 -0
- package/scripts/migrate-project-state.mjs +176 -0
- package/scripts/pi-auto.sh +79 -0
- package/scripts/pi-model-catalog.sh +154 -0
- package/scripts/pi-session-stats.sh +135 -0
- package/scripts/piagent-cli.mjs +53 -0
- package/scripts/profile-doctor.sh +238 -0
- package/scripts/quality-benchmark.sh +185 -0
- package/scripts/redaction-benchmark.mjs +227 -0
- package/scripts/runtime-policy-smoke.sh +48 -0
- package/scripts/setup.sh +425 -0
- package/scripts/team-doctor.sh +461 -0
- package/scripts/verify-local.sh +540 -0
- package/scripts/verify-release-identity.mjs +85 -0
- package/scripts/verify-vercel-link.mjs +64 -0
- package/templates/global/mcp.json +9 -0
- package/templates/global/settings.json +37 -0
- package/templates/project/.mcp.json +9 -0
- package/templates/project/.pi/context-index.json +25 -0
- package/templates/project/.pi/gitignore.template +22 -0
- package/templates/project/.pi/mcp.json +9 -0
- package/templates/project/.pi/memory/MEMORY.md +11 -0
- package/templates/project/.pi/memory/memory_summary.md +9 -0
- package/templates/project/.pi/piagent-profile.json +107 -0
- package/templates/project/.pi/project-context.md +30 -0
- package/templates/project/.pi/settings.json +17 -0
- package/templates/project/.pi/task-contract.template.json +74 -0
- package/templates/project/.pi/tech-context/README.md +7 -0
- package/templates/project/.pi/tech-stack.json +10 -0
- package/templates/project/AGENTS.md +34 -0
- package/templates/project/REVIEW_GUIDELINES.md +23 -0
- package/types/pi-runtime-shims.d.ts +13 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Converts a project from the pre-piagent layout to the current one.
|
|
3
|
+
//
|
|
4
|
+
// This runs once per project. There is no alias layer, so a project that
|
|
5
|
+
// still carries the old layout cannot be served by the current guard at all;
|
|
6
|
+
// converting is the only path forward. The old files are left in place until
|
|
7
|
+
// the caller confirms removal, so an interrupted run is always recoverable.
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
const RENAMES = [
|
|
12
|
+
{ from: ".pi/company-profile.json", to: ".pi/piagent-profile.json" },
|
|
13
|
+
{ from: ".pi/company-profile.lock.json", to: ".pi/piagent-profile.lock.json" },
|
|
14
|
+
{ from: ".pi/company-state", to: ".pi/piagent-state" }
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
// Applied to the text of migrated JSON and to AGENTS.md, which commonly
|
|
18
|
+
// references tool names and profile paths in its instructions.
|
|
19
|
+
const TEXT_REPLACEMENTS = [
|
|
20
|
+
["PI_COMPANY_", "PIAGENT_"],
|
|
21
|
+
["pi.company/", "piagent/"],
|
|
22
|
+
["pi-company-", "piagent-"],
|
|
23
|
+
["company_", "piagent_"],
|
|
24
|
+
["company-profile", "piagent-profile"],
|
|
25
|
+
["company-state", "piagent-state"]
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function usage() {
|
|
29
|
+
console.log(`Usage:
|
|
30
|
+
piagent-migrate [project-path] [--apply] [--remove-old]
|
|
31
|
+
|
|
32
|
+
Converts project-local state from the pre-piagent layout.
|
|
33
|
+
|
|
34
|
+
(default) Dry run. Reports what would change and exits 0.
|
|
35
|
+
--apply Write the new layout.
|
|
36
|
+
--remove-old With --apply, delete the old files once the new ones exist.
|
|
37
|
+
|
|
38
|
+
After --apply, regenerate the capability lock:
|
|
39
|
+
piagent-capabilities resolve --profile .pi/piagent-profile.json \\
|
|
40
|
+
--output .pi/piagent-profile.lock.json --package-source <source>`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const args = process.argv.slice(2);
|
|
44
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
45
|
+
usage();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const apply = args.includes("--apply");
|
|
50
|
+
const removeOld = args.includes("--remove-old");
|
|
51
|
+
const positional = args.filter((arg) => !arg.startsWith("--"));
|
|
52
|
+
if (positional.length > 1) {
|
|
53
|
+
console.error(`Expected at most one project path, received ${positional.length}.`);
|
|
54
|
+
usage();
|
|
55
|
+
process.exit(2);
|
|
56
|
+
}
|
|
57
|
+
if (removeOld && !apply) {
|
|
58
|
+
console.error("--remove-old requires --apply.");
|
|
59
|
+
process.exit(2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const projectPath = path.resolve(positional[0] ?? process.cwd());
|
|
63
|
+
if (!fs.existsSync(projectPath)) {
|
|
64
|
+
console.error(`Project path does not exist: ${projectPath}`);
|
|
65
|
+
process.exit(2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function rewriteText(text) {
|
|
69
|
+
let output = text;
|
|
70
|
+
for (const [from, to] of TEXT_REPLACEMENTS) output = output.split(from).join(to);
|
|
71
|
+
return output;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function copyRecursive(from, to) {
|
|
75
|
+
const stat = fs.lstatSync(from);
|
|
76
|
+
if (stat.isSymbolicLink()) return { skipped: from, reason: "symlink" };
|
|
77
|
+
if (stat.isDirectory()) {
|
|
78
|
+
fs.mkdirSync(to, { recursive: true });
|
|
79
|
+
for (const entry of fs.readdirSync(from)) copyRecursive(path.join(from, entry), path.join(to, entry));
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
if (from.endsWith(".json") || from.endsWith(".md") || from.endsWith(".jsonl")) {
|
|
83
|
+
fs.writeFileSync(to, rewriteText(fs.readFileSync(from, "utf8")));
|
|
84
|
+
} else {
|
|
85
|
+
fs.copyFileSync(from, to);
|
|
86
|
+
}
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const planned = [];
|
|
91
|
+
const skipped = [];
|
|
92
|
+
// Source and target both present means a previous --apply already copied this
|
|
93
|
+
// entry and the caller has not cleaned up yet. That is the normal midpoint of
|
|
94
|
+
// the two-step workflow, not a conflict, so it stays eligible for --remove-old.
|
|
95
|
+
const alreadyMigrated = [];
|
|
96
|
+
|
|
97
|
+
for (const { from, to } of RENAMES) {
|
|
98
|
+
const source = path.join(projectPath, from);
|
|
99
|
+
const target = path.join(projectPath, to);
|
|
100
|
+
if (!fs.existsSync(source)) continue;
|
|
101
|
+
if (fs.existsSync(target)) {
|
|
102
|
+
alreadyMigrated.push({ from, to });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
planned.push({ from, to });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// AGENTS.md is rewritten in place because it is the instruction file the guard
|
|
109
|
+
// reads; leaving old tool names there would keep pointing the agent at tools
|
|
110
|
+
// that no longer exist.
|
|
111
|
+
const agentsPath = path.join(projectPath, "AGENTS.md");
|
|
112
|
+
let agentsNeedsRewrite = false;
|
|
113
|
+
if (fs.existsSync(agentsPath)) {
|
|
114
|
+
const text = fs.readFileSync(agentsPath, "utf8");
|
|
115
|
+
agentsNeedsRewrite = rewriteText(text) !== text;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const nothingToDo = planned.length === 0 && alreadyMigrated.length === 0 && !agentsNeedsRewrite;
|
|
119
|
+
|
|
120
|
+
if (nothingToDo) {
|
|
121
|
+
console.log(JSON.stringify({ ok: true, projectPath, migrated: false, reason: "already on the current layout" }, null, 2));
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!apply) {
|
|
126
|
+
console.log(JSON.stringify({
|
|
127
|
+
ok: true,
|
|
128
|
+
projectPath,
|
|
129
|
+
dryRun: true,
|
|
130
|
+
wouldRename: planned,
|
|
131
|
+
wouldRewrite: agentsNeedsRewrite ? ["AGENTS.md"] : [],
|
|
132
|
+
alreadyMigrated,
|
|
133
|
+
next: alreadyMigrated.length > 0 && planned.length === 0
|
|
134
|
+
? "re-run with --apply --remove-old to delete the originals"
|
|
135
|
+
: "re-run with --apply"
|
|
136
|
+
}, null, 2));
|
|
137
|
+
process.exit(0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const renamed = [];
|
|
141
|
+
for (const { from, to } of planned) {
|
|
142
|
+
const result = copyRecursive(path.join(projectPath, from), path.join(projectPath, to));
|
|
143
|
+
if (result.skipped) {
|
|
144
|
+
skipped.push({ path: from, reason: result.reason });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
renamed.push({ from, to });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (agentsNeedsRewrite) {
|
|
151
|
+
fs.writeFileSync(agentsPath, rewriteText(fs.readFileSync(agentsPath, "utf8")));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const removed = [];
|
|
155
|
+
if (removeOld) {
|
|
156
|
+
for (const { from } of [...renamed, ...alreadyMigrated]) {
|
|
157
|
+
fs.rmSync(path.join(projectPath, from), { recursive: true, force: true });
|
|
158
|
+
removed.push(from);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const pendingCleanup = renamed.length + alreadyMigrated.length - removed.length;
|
|
163
|
+
|
|
164
|
+
console.log(JSON.stringify({
|
|
165
|
+
ok: true,
|
|
166
|
+
projectPath,
|
|
167
|
+
migrated: true,
|
|
168
|
+
renamed,
|
|
169
|
+
alreadyMigrated,
|
|
170
|
+
rewrote: agentsNeedsRewrite ? ["AGENTS.md"] : [],
|
|
171
|
+
skipped,
|
|
172
|
+
removedOld: removed,
|
|
173
|
+
next: pendingCleanup > 0
|
|
174
|
+
? "verify the new files, then re-run with --apply --remove-old to delete the originals"
|
|
175
|
+
: "regenerate the capability lock with piagent-capabilities resolve"
|
|
176
|
+
}, null, 2));
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'USAGE'
|
|
6
|
+
Usage:
|
|
7
|
+
piagent-auto [--read-only|--workspace-write|--full-access] [--no-approve] [--] [pi args...]
|
|
8
|
+
|
|
9
|
+
Purpose:
|
|
10
|
+
Launch Pi with project-local trust approved for this run.
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
piagent-auto
|
|
14
|
+
piagent-auto --name "payment scout"
|
|
15
|
+
piagent-auto --read-only -p "Scout payment mapping. Do not edit source."
|
|
16
|
+
piagent-auto --full-access -p "Run the trusted local benchmark suite."
|
|
17
|
+
piagent-auto -- --model openai-codex/gpt-5.5:xhigh
|
|
18
|
+
|
|
19
|
+
Notes:
|
|
20
|
+
- This wraps `pi --approve`; it does not disable Piagent guardrails.
|
|
21
|
+
- Protected paths, destructive shell checks, task gates, and verify evidence still run.
|
|
22
|
+
- Use `--read-only` for guard-enforced read-only mode.
|
|
23
|
+
- Use `--full-access` only for trusted projects in an externally safe environment.
|
|
24
|
+
- Use `--no-approve` to force Pi to ignore project-local resources for this run.
|
|
25
|
+
USAGE
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
PERMISSION_PROFILE="workspace-write"
|
|
29
|
+
APPROVE_FLAG="--approve"
|
|
30
|
+
ARGS=()
|
|
31
|
+
|
|
32
|
+
while [[ $# -gt 0 ]]; do
|
|
33
|
+
case "$1" in
|
|
34
|
+
--help|-h)
|
|
35
|
+
usage
|
|
36
|
+
exit 0
|
|
37
|
+
;;
|
|
38
|
+
--read-only)
|
|
39
|
+
PERMISSION_PROFILE="read-only"
|
|
40
|
+
shift
|
|
41
|
+
;;
|
|
42
|
+
--workspace-write)
|
|
43
|
+
PERMISSION_PROFILE="workspace-write"
|
|
44
|
+
shift
|
|
45
|
+
;;
|
|
46
|
+
--full-access|--trusted-full-access)
|
|
47
|
+
PERMISSION_PROFILE="trusted-full-access"
|
|
48
|
+
shift
|
|
49
|
+
;;
|
|
50
|
+
--no-approve|-na)
|
|
51
|
+
APPROVE_FLAG="--no-approve"
|
|
52
|
+
shift
|
|
53
|
+
;;
|
|
54
|
+
--approve|-a)
|
|
55
|
+
APPROVE_FLAG="--approve"
|
|
56
|
+
shift
|
|
57
|
+
;;
|
|
58
|
+
--)
|
|
59
|
+
shift
|
|
60
|
+
ARGS+=("$@")
|
|
61
|
+
break
|
|
62
|
+
;;
|
|
63
|
+
*)
|
|
64
|
+
ARGS+=("$1")
|
|
65
|
+
shift
|
|
66
|
+
;;
|
|
67
|
+
esac
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
if ! command -v pi >/dev/null 2>&1; then
|
|
71
|
+
echo "FAIL: pi is not on PATH. Run piagent-setup or follow the exact-version install flow in docs/release-install-policy.md." >&2
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
if [[ "$PERMISSION_PROFILE" == "read-only" ]]; then
|
|
76
|
+
exec env PIAGENT_PERMISSION_PROFILE="$PERMISSION_PROFILE" pi "$APPROVE_FLAG" --exclude-tools bash,write,edit "${ARGS[@]}"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
exec env PIAGENT_PERMISSION_PROFILE="$PERMISSION_PROFILE" pi "$APPROVE_FLAG" "${ARGS[@]}"
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'USAGE'
|
|
6
|
+
Usage:
|
|
7
|
+
scripts/pi-model-catalog.sh [--json] [--provider <provider>]
|
|
8
|
+
|
|
9
|
+
Examples:
|
|
10
|
+
scripts/pi-model-catalog.sh
|
|
11
|
+
scripts/pi-model-catalog.sh --provider openai-codex
|
|
12
|
+
scripts/pi-model-catalog.sh --provider anthropic --json
|
|
13
|
+
|
|
14
|
+
Notes:
|
|
15
|
+
- Reads Pi's local model catalog at ~/.pi/agent/models-store.json by default.
|
|
16
|
+
- Run `pi update --models` first when you want the newest provider catalog.
|
|
17
|
+
- `pi --list-models` may hide models until a provider credential is available;
|
|
18
|
+
this script reads the local catalog directly so docs/presets can be reviewed.
|
|
19
|
+
USAGE
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
FORMAT="text"
|
|
23
|
+
PROVIDER=""
|
|
24
|
+
|
|
25
|
+
while [[ $# -gt 0 ]]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--json)
|
|
28
|
+
FORMAT="json"
|
|
29
|
+
shift
|
|
30
|
+
;;
|
|
31
|
+
--provider)
|
|
32
|
+
PROVIDER="${2:-}"
|
|
33
|
+
shift 2
|
|
34
|
+
;;
|
|
35
|
+
-h|--help)
|
|
36
|
+
usage
|
|
37
|
+
exit 0
|
|
38
|
+
;;
|
|
39
|
+
*)
|
|
40
|
+
echo "Unknown argument: $1" >&2
|
|
41
|
+
usage >&2
|
|
42
|
+
exit 2
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
node --input-type=module - "$FORMAT" "$PROVIDER" <<'NODE'
|
|
48
|
+
import fs from "node:fs";
|
|
49
|
+
import os from "node:os";
|
|
50
|
+
import path from "node:path";
|
|
51
|
+
|
|
52
|
+
const [format, providerFilter] = process.argv.slice(2);
|
|
53
|
+
const agentDir = process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
|
|
54
|
+
const catalogPath = path.join(agentDir, "models-store.json");
|
|
55
|
+
|
|
56
|
+
if (!fs.existsSync(catalogPath)) {
|
|
57
|
+
console.error(`FAIL: Pi model catalog not found: ${catalogPath}`);
|
|
58
|
+
console.error("Hint: run `pi update --models` or open Pi once after login.");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const catalog = JSON.parse(fs.readFileSync(catalogPath, "utf8"));
|
|
63
|
+
const targetProviders = providerFilter ? [providerFilter] : ["openai-codex", "anthropic"];
|
|
64
|
+
|
|
65
|
+
function explicitThinkingLevels(model) {
|
|
66
|
+
if (!model.reasoning) return "no";
|
|
67
|
+
const map = model.thinkingLevelMap;
|
|
68
|
+
if (!map || typeof map !== "object") return "yes";
|
|
69
|
+
const levels = Object.entries(map)
|
|
70
|
+
.filter(([, value]) => value !== null && value !== false)
|
|
71
|
+
.map(([key]) => key);
|
|
72
|
+
return levels.length > 0 ? levels.join(",") : "yes";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function recommendedThinking(model) {
|
|
76
|
+
if (!model.reasoning) return "";
|
|
77
|
+
const raw = explicitThinkingLevels(model);
|
|
78
|
+
if (raw === "yes") return "xhigh";
|
|
79
|
+
if (raw === "no") return "";
|
|
80
|
+
const levels = raw.split(",");
|
|
81
|
+
for (const preferred of ["xhigh", "max", "high", "medium", "low", "minimal"]) {
|
|
82
|
+
if (levels.includes(preferred)) return preferred;
|
|
83
|
+
}
|
|
84
|
+
return levels[0] ?? "xhigh";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function roleFor(provider, id) {
|
|
88
|
+
if (provider === "openai-codex") {
|
|
89
|
+
if (/mini|spark/i.test(id)) return "fast";
|
|
90
|
+
if (/5\.4$/.test(id)) return "balanced";
|
|
91
|
+
if (/5\.5/.test(id)) return "balanced,hard";
|
|
92
|
+
if (/luna/i.test(id)) return "focused-hard";
|
|
93
|
+
if (/sol/i.test(id)) return "strategic-deep";
|
|
94
|
+
if (/terra/i.test(id)) return "huge-context";
|
|
95
|
+
return "general";
|
|
96
|
+
}
|
|
97
|
+
if (provider === "anthropic") {
|
|
98
|
+
if (/haiku/i.test(id)) return "fast";
|
|
99
|
+
if (/sonnet/i.test(id)) return "balanced,hard";
|
|
100
|
+
if (/opus/i.test(id)) return "strategic-deep";
|
|
101
|
+
if (/fable/i.test(id)) return "huge-context,deep";
|
|
102
|
+
return "general";
|
|
103
|
+
}
|
|
104
|
+
return "general";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const rows = [];
|
|
108
|
+
for (const provider of targetProviders) {
|
|
109
|
+
const models = catalog[provider]?.models ?? [];
|
|
110
|
+
for (const model of models) {
|
|
111
|
+
rows.push({
|
|
112
|
+
provider,
|
|
113
|
+
id: model.id,
|
|
114
|
+
name: model.name ?? model.id,
|
|
115
|
+
contextWindow: model.contextWindow ?? null,
|
|
116
|
+
maxTokens: model.maxTokens ?? null,
|
|
117
|
+
reasoning: Boolean(model.reasoning),
|
|
118
|
+
thinking: explicitThinkingLevels(model),
|
|
119
|
+
recommendedThinking: recommendedThinking(model),
|
|
120
|
+
images: Array.isArray(model.input) && model.input.includes("image"),
|
|
121
|
+
role: roleFor(provider, model.id),
|
|
122
|
+
example: `${provider}/${model.id}${model.reasoning ? `:${recommendedThinking(model)}` : ""}`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (format === "json") {
|
|
128
|
+
console.log(JSON.stringify({ catalogPath, providers: targetProviders, models: rows }, null, 2));
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const number = (value) => value === null || value === undefined ? "?" : Number(value).toLocaleString("en-US");
|
|
133
|
+
console.log(`# Pi Agent model catalog`);
|
|
134
|
+
console.log();
|
|
135
|
+
console.log(`Source: ${catalogPath}`);
|
|
136
|
+
console.log(`Tip: run \`pi update --models\` before checking latest model versions.`);
|
|
137
|
+
console.log();
|
|
138
|
+
for (const provider of targetProviders) {
|
|
139
|
+
const providerRows = rows.filter((row) => row.provider === provider);
|
|
140
|
+
console.log(`## ${provider}`);
|
|
141
|
+
console.log();
|
|
142
|
+
if (providerRows.length === 0) {
|
|
143
|
+
console.log("_No models found in local catalog._");
|
|
144
|
+
console.log();
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
console.log("| Model | Role | Context | Max output | Thinking | Images | Command example |");
|
|
148
|
+
console.log("|---|---|---:|---:|---|---|---|");
|
|
149
|
+
for (const row of providerRows) {
|
|
150
|
+
console.log(`| \`${row.id}\` | ${row.role} | ${number(row.contextWindow)} | ${number(row.maxTokens)} | ${row.thinking} | ${row.images ? "yes" : "no"} | \`pi --model ${row.example}\` |`);
|
|
151
|
+
}
|
|
152
|
+
console.log();
|
|
153
|
+
}
|
|
154
|
+
NODE
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'USAGE'
|
|
6
|
+
Usage:
|
|
7
|
+
scripts/pi-session-stats.sh [project-path] [session-file]
|
|
8
|
+
|
|
9
|
+
Examples:
|
|
10
|
+
scripts/pi-session-stats.sh .
|
|
11
|
+
scripts/pi-session-stats.sh /path/to/project
|
|
12
|
+
scripts/pi-session-stats.sh /path/to/project ~/.pi/agent/sessions/.../session.jsonl
|
|
13
|
+
|
|
14
|
+
Notes:
|
|
15
|
+
- Reads exact Pi token/cache/cost totals via RPC get_session_stats.
|
|
16
|
+
- If session-file is omitted, the newest persisted session whose header cwd matches project-path is used.
|
|
17
|
+
USAGE
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
21
|
+
usage
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
PROJECT_PATH="${1:-.}"
|
|
26
|
+
SESSION_FILE="${2:-}"
|
|
27
|
+
|
|
28
|
+
if [[ ! -d "$PROJECT_PATH" ]]; then
|
|
29
|
+
echo "FAIL: project path does not exist: $PROJECT_PATH" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
PROJECT_PATH="$(cd "$PROJECT_PATH" && pwd)"
|
|
34
|
+
|
|
35
|
+
if [[ -z "$SESSION_FILE" ]]; then
|
|
36
|
+
SESSION_FILE="$(node --input-type=module - "$PROJECT_PATH" <<'NODE'
|
|
37
|
+
import fs from "node:fs";
|
|
38
|
+
import os from "node:os";
|
|
39
|
+
import path from "node:path";
|
|
40
|
+
|
|
41
|
+
const projectPath = process.argv[2];
|
|
42
|
+
const agentDir = process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
|
|
43
|
+
const sessionsDir = process.env.PI_CODING_AGENT_SESSION_DIR || path.join(agentDir, "sessions");
|
|
44
|
+
const candidates = [];
|
|
45
|
+
|
|
46
|
+
function walk(dir) {
|
|
47
|
+
if (!fs.existsSync(dir)) return;
|
|
48
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
49
|
+
const target = path.join(dir, entry.name);
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
walk(target);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
55
|
+
try {
|
|
56
|
+
const firstLine = fs.readFileSync(target, "utf8").split(/\n/, 1)[0];
|
|
57
|
+
const header = JSON.parse(firstLine);
|
|
58
|
+
if (header.cwd !== projectPath) continue;
|
|
59
|
+
const stat = fs.statSync(target);
|
|
60
|
+
candidates.push({ target, mtimeMs: stat.mtimeMs });
|
|
61
|
+
} catch {
|
|
62
|
+
// Ignore malformed or inaccessible session files.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
walk(sessionsDir);
|
|
68
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
69
|
+
if (candidates[0]) process.stdout.write(candidates[0].target);
|
|
70
|
+
NODE
|
|
71
|
+
)"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
if [[ -z "$SESSION_FILE" || ! -f "$SESSION_FILE" ]]; then
|
|
75
|
+
echo "FAIL: no Pi session file found for project: $PROJECT_PATH" >&2
|
|
76
|
+
echo "Hint: run /session inside Pi, or pass the session file explicitly." >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
RPC_OUTPUT="$(
|
|
81
|
+
printf '%s\n' '{"type":"get_session_stats","id":"piagent-usage"}' \
|
|
82
|
+
| pi --mode rpc --session "$SESSION_FILE" --offline --approve
|
|
83
|
+
)"
|
|
84
|
+
|
|
85
|
+
node --input-type=module - "$RPC_OUTPUT" <<'NODE'
|
|
86
|
+
const input = process.argv[2] ?? "";
|
|
87
|
+
|
|
88
|
+
const line = input
|
|
89
|
+
.split(/\n+/)
|
|
90
|
+
.find((item) => item.includes('"command":"get_session_stats"'));
|
|
91
|
+
if (!line) {
|
|
92
|
+
console.error("FAIL: get_session_stats response not found");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const response = JSON.parse(line);
|
|
96
|
+
if (!response.success) {
|
|
97
|
+
console.error(`FAIL: ${response.error ?? "get_session_stats failed"}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
const data = response.data;
|
|
101
|
+
const tokens = data.tokens ?? {};
|
|
102
|
+
const context = data.contextUsage ?? {};
|
|
103
|
+
const number = (value) => value === null || value === undefined ? "unknown" : Math.round(Number(value)).toLocaleString("en-US");
|
|
104
|
+
const money = (value) => value === null || value === undefined ? "unknown" : `$${Number(value).toFixed(6)}`;
|
|
105
|
+
const percent = (value) => value === null || value === undefined ? "unknown" : `${Number(value).toFixed(1)}%`;
|
|
106
|
+
console.log(JSON.stringify({
|
|
107
|
+
sessionFile: data.sessionFile,
|
|
108
|
+
sessionId: data.sessionId,
|
|
109
|
+
messages: {
|
|
110
|
+
user: data.userMessages,
|
|
111
|
+
assistant: data.assistantMessages,
|
|
112
|
+
toolCalls: data.toolCalls,
|
|
113
|
+
toolResults: data.toolResults,
|
|
114
|
+
total: data.totalMessages
|
|
115
|
+
},
|
|
116
|
+
tokens: {
|
|
117
|
+
input: tokens.input ?? null,
|
|
118
|
+
output: tokens.output ?? null,
|
|
119
|
+
cacheRead: tokens.cacheRead ?? null,
|
|
120
|
+
cacheWrite: tokens.cacheWrite ?? null,
|
|
121
|
+
total: tokens.total ?? null
|
|
122
|
+
},
|
|
123
|
+
cost: data.cost ?? null,
|
|
124
|
+
contextUsage: context.tokens === undefined ? null : {
|
|
125
|
+
tokens: context.tokens,
|
|
126
|
+
contextWindow: context.contextWindow,
|
|
127
|
+
percent: context.percent
|
|
128
|
+
},
|
|
129
|
+
summary: {
|
|
130
|
+
tokens: `${number(tokens.total)} total (${number(tokens.input)} input, ${number(tokens.output)} output, ${number(tokens.cacheRead)} cache read, ${number(tokens.cacheWrite)} cache write)`,
|
|
131
|
+
cost: money(data.cost),
|
|
132
|
+
context: context.tokens === undefined ? "unknown" : `${number(context.tokens)} / ${number(context.contextWindow)} (${percent(context.percent)})`
|
|
133
|
+
}
|
|
134
|
+
}, null, 2));
|
|
135
|
+
NODE
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const scriptByCommand = {
|
|
7
|
+
"piagent-setup": "scripts/setup.sh",
|
|
8
|
+
"piagent-install": "scripts/install-global.sh",
|
|
9
|
+
"piagent-init": "scripts/init-project.sh",
|
|
10
|
+
"piagent-doctor": "scripts/team-doctor.sh",
|
|
11
|
+
"piagent-benchmark": "scripts/quality-benchmark.sh",
|
|
12
|
+
"piagent-usage": "scripts/pi-session-stats.sh",
|
|
13
|
+
"piagent-models": "scripts/pi-model-catalog.sh",
|
|
14
|
+
"piagent-model-scope": "scripts/configure-model-scope.sh",
|
|
15
|
+
"piagent-mcp": "scripts/configure-mcp.sh",
|
|
16
|
+
"piagent-subagents": "scripts/configure-subagents.sh",
|
|
17
|
+
"piagent-capabilities": "scripts/capability-catalog.mjs",
|
|
18
|
+
"piagent-migrate": "scripts/migrate-project-state.mjs",
|
|
19
|
+
"piagent-import-instructions": "scripts/import-agent-instructions.mjs",
|
|
20
|
+
"piagent-auto": "scripts/pi-auto.sh"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
24
|
+
const invokedAs = path.basename(process.argv[1] ?? "");
|
|
25
|
+
const script = scriptByCommand[invokedAs];
|
|
26
|
+
|
|
27
|
+
if (!script) {
|
|
28
|
+
console.error(`Unknown Pi Agent command: ${invokedAs || "(unknown)"}`);
|
|
29
|
+
console.error(`Expected one of: ${Object.keys(scriptByCommand).sort().join(", ")}`);
|
|
30
|
+
process.exit(2);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const target = path.join(packageRoot, script);
|
|
34
|
+
const runner = target.endsWith(".mjs") ? process.execPath : "bash";
|
|
35
|
+
const child = spawn(runner, [target, ...process.argv.slice(2)], {
|
|
36
|
+
cwd: process.cwd(),
|
|
37
|
+
env: process.env,
|
|
38
|
+
stdio: "inherit"
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
child.once("error", (error) => {
|
|
42
|
+
const code = error && typeof error === "object" && "code" in error ? ` (${error.code})` : "";
|
|
43
|
+
console.error(`Pi Agent command could not start ${runner}${code}. Ensure it is installed and available on PATH.`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.once("exit", (code, signal) => {
|
|
48
|
+
if (signal) {
|
|
49
|
+
process.kill(process.pid, signal);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
process.exit(code ?? 1);
|
|
53
|
+
});
|