@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.
Files changed (159) hide show
  1. package/.gitattributes +7 -0
  2. package/AGENTS.md +31 -0
  3. package/CHANGELOG.md +296 -0
  4. package/CODE_OF_CONDUCT.md +36 -0
  5. package/CONTRIBUTING.md +66 -0
  6. package/LICENSE +21 -0
  7. package/README.md +588 -0
  8. package/SECURITY.md +38 -0
  9. package/adapters/backend-api/profile.json +88 -0
  10. package/adapters/be-readonly-fe/profile.json +120 -0
  11. package/adapters/data/profile.json +88 -0
  12. package/adapters/devops/profile.json +92 -0
  13. package/adapters/docs/profile.json +74 -0
  14. package/adapters/fullstack/profile.json +94 -0
  15. package/adapters/generic/profile.json +95 -0
  16. package/adapters/mobile/profile.json +87 -0
  17. package/adapters/node-typescript/profile.json +76 -0
  18. package/adapters/python/profile.json +75 -0
  19. package/adapters/web-frontend/profile.json +101 -0
  20. package/catalog/capabilities.json +206 -0
  21. package/docs/architecture.md +64 -0
  22. package/docs/auto-delegation-policy.md +192 -0
  23. package/docs/capability-packs.md +234 -0
  24. package/docs/command-reference-vietnamese.md +484 -0
  25. package/docs/context-window-policy.md +85 -0
  26. package/docs/distribution-standard.md +229 -0
  27. package/docs/herdr-workflow.md +58 -0
  28. package/docs/mcp-and-tools.md +316 -0
  29. package/docs/memory-policy.md +164 -0
  30. package/docs/model-options.md +165 -0
  31. package/docs/oauth-providers.md +53 -0
  32. package/docs/operator-manual-vietnamese.md +1080 -0
  33. package/docs/package-architecture-notes.md +101 -0
  34. package/docs/project-adapters.md +174 -0
  35. package/docs/project-onboarding.md +135 -0
  36. package/docs/publishing-for-teams.md +64 -0
  37. package/docs/quality-benchmark.md +74 -0
  38. package/docs/quickstart-vietnamese.md +350 -0
  39. package/docs/release-install-policy.md +223 -0
  40. package/docs/runtime-harness-standard.md +118 -0
  41. package/docs/runtime-policy-design.md +132 -0
  42. package/docs/runtime-quality-baseline.md +257 -0
  43. package/docs/security-redaction-benchmark.md +56 -0
  44. package/docs/security-threat-model.md +66 -0
  45. package/docs/subagent-orchestration-capabilities.md +104 -0
  46. package/docs/subagents-and-multiagent.md +346 -0
  47. package/docs/task-implementation-contract.md +200 -0
  48. package/docs/team-onboarding.md +333 -0
  49. package/docs/usage-observability.md +130 -0
  50. package/docs/vercel-docs-site.md +115 -0
  51. package/docs/workflow-recipes.md +125 -0
  52. package/docs-site/assets/piagent-logo.svg +35 -0
  53. package/docs-site/favicon.svg +18 -0
  54. package/docs-site/index.html +1613 -0
  55. package/docs-site/vercel.json +24 -0
  56. package/evals/fixtures/action-proposal.invalid.json +34 -0
  57. package/evals/fixtures/action-proposal.valid.json +34 -0
  58. package/evals/fixtures/capability-pack.invalid.json +123 -0
  59. package/evals/fixtures/capability-pack.valid.json +123 -0
  60. package/evals/fixtures/capability-recipe.invalid.json +81 -0
  61. package/evals/fixtures/capability-recipe.valid.json +82 -0
  62. package/evals/fixtures/context-index.invalid.json +25 -0
  63. package/evals/fixtures/context-index.valid.json +25 -0
  64. package/evals/fixtures/eval-scenario.invalid.json +40 -0
  65. package/evals/fixtures/eval-scenario.valid.json +45 -0
  66. package/evals/fixtures/project-profile.invalid.json +88 -0
  67. package/evals/fixtures/project-profile.valid.json +95 -0
  68. package/evals/fixtures/task-contract.invalid.json +73 -0
  69. package/evals/fixtures/task-contract.valid.json +74 -0
  70. package/evals/golden/enforcement-decisions.json +329 -0
  71. package/evals/scenarios/capability-resolution.json +45 -0
  72. package/package.json +132 -0
  73. package/packages/piagent-core/README.md +147 -0
  74. package/packages/piagent-core/capabilities/capability-core.js +1023 -0
  75. package/packages/piagent-core/capabilities/capability-sources.js +265 -0
  76. package/packages/piagent-core/extensions/guard-io.js +45 -0
  77. package/packages/piagent-core/extensions/guard-shell-analysis.ts +558 -0
  78. package/packages/piagent-core/extensions/guard-types.ts +406 -0
  79. package/packages/piagent-core/extensions/piagent-guard.ts +5760 -0
  80. package/packages/piagent-core/extensions/policy-core.js +1070 -0
  81. package/packages/piagent-core/extensions/redaction-core.js +4 -0
  82. package/packages/piagent-core/extensions/runtime-evidence.js +215 -0
  83. package/packages/piagent-core/package.json +46 -0
  84. package/packages/piagent-core/policies/base-policy.json +249 -0
  85. package/packages/piagent-core/prompts/be-to-fe.md +82 -0
  86. package/packages/piagent-core/prompts/commit.md +27 -0
  87. package/packages/piagent-core/prompts/discuss.md +35 -0
  88. package/packages/piagent-core/prompts/memory-policy.md +52 -0
  89. package/packages/piagent-core/prompts/model-options.md +68 -0
  90. package/packages/piagent-core/prompts/onboard-project.md +147 -0
  91. package/packages/piagent-core/prompts/piagent-commands.md +68 -0
  92. package/packages/piagent-core/prompts/plan.md +35 -0
  93. package/packages/piagent-core/prompts/platform-improve.md +69 -0
  94. package/packages/piagent-core/prompts/pr.md +26 -0
  95. package/packages/piagent-core/prompts/review.md +38 -0
  96. package/packages/piagent-core/prompts/scout.md +50 -0
  97. package/packages/piagent-core/prompts/task.md +69 -0
  98. package/packages/piagent-core/security/sensitive-data.js +187 -0
  99. package/packages/piagent-core/skills/piagent-ops/SKILL.md +44 -0
  100. package/packages/piagent-core/skills/piagent-source-cache/SKILL.md +59 -0
  101. package/packages/piagent-core/skills/piagent-source-cache/checkout-source-repo.sh +144 -0
  102. package/packages/piagent-core/subagents/piagent-oracle.md +29 -0
  103. package/packages/piagent-core/subagents/piagent-planner.md +46 -0
  104. package/packages/piagent-core/subagents/piagent-reviewer.md +48 -0
  105. package/packages/piagent-core/subagents/piagent-scout.md +37 -0
  106. package/packages/piagent-core/subagents/piagent-worker.md +44 -0
  107. package/packs/engineering-base/pack.json +123 -0
  108. package/packs/engineering-base/recipes/bounded-change.json +82 -0
  109. package/packs/web-delivery/pack.json +72 -0
  110. package/packs/web-delivery/recipes/verified-web-change.json +57 -0
  111. package/schemas/action-proposal.schema.json +90 -0
  112. package/schemas/capability-pack.schema.json +190 -0
  113. package/schemas/capability-recipe.schema.json +72 -0
  114. package/schemas/context-index.schema.json +258 -0
  115. package/schemas/eval-scenario.schema.json +67 -0
  116. package/schemas/project-profile.schema.json +555 -0
  117. package/schemas/task-contract.schema.json +314 -0
  118. package/scripts/audit-runtime-host.sh +78 -0
  119. package/scripts/bootstrap-local.sh +25 -0
  120. package/scripts/capability-catalog.mjs +290 -0
  121. package/scripts/check-runtime-advisories.mjs +114 -0
  122. package/scripts/configure-mcp.sh +301 -0
  123. package/scripts/configure-model-scope.sh +143 -0
  124. package/scripts/configure-subagents.sh +241 -0
  125. package/scripts/import-agent-instructions.mjs +199 -0
  126. package/scripts/init-project.sh +369 -0
  127. package/scripts/install-global.sh +532 -0
  128. package/scripts/link-project.sh +25 -0
  129. package/scripts/migrate-project-state.mjs +176 -0
  130. package/scripts/pi-auto.sh +79 -0
  131. package/scripts/pi-model-catalog.sh +154 -0
  132. package/scripts/pi-session-stats.sh +135 -0
  133. package/scripts/piagent-cli.mjs +53 -0
  134. package/scripts/profile-doctor.sh +238 -0
  135. package/scripts/quality-benchmark.sh +185 -0
  136. package/scripts/redaction-benchmark.mjs +227 -0
  137. package/scripts/runtime-policy-smoke.sh +48 -0
  138. package/scripts/setup.sh +425 -0
  139. package/scripts/team-doctor.sh +461 -0
  140. package/scripts/verify-local.sh +540 -0
  141. package/scripts/verify-release-identity.mjs +85 -0
  142. package/scripts/verify-vercel-link.mjs +64 -0
  143. package/templates/global/mcp.json +9 -0
  144. package/templates/global/settings.json +37 -0
  145. package/templates/project/.mcp.json +9 -0
  146. package/templates/project/.pi/context-index.json +25 -0
  147. package/templates/project/.pi/gitignore.template +22 -0
  148. package/templates/project/.pi/mcp.json +9 -0
  149. package/templates/project/.pi/memory/MEMORY.md +11 -0
  150. package/templates/project/.pi/memory/memory_summary.md +9 -0
  151. package/templates/project/.pi/piagent-profile.json +107 -0
  152. package/templates/project/.pi/project-context.md +30 -0
  153. package/templates/project/.pi/settings.json +17 -0
  154. package/templates/project/.pi/task-contract.template.json +74 -0
  155. package/templates/project/.pi/tech-context/README.md +7 -0
  156. package/templates/project/.pi/tech-stack.json +10 -0
  157. package/templates/project/AGENTS.md +34 -0
  158. package/templates/project/REVIEW_GUIDELINES.md +23 -0
  159. 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
+ });