@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/AGENTSAM.md +55 -0
  2. package/README.md +12 -8
  3. package/bin/agentsam +2 -0
  4. package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
  5. package/docs/CLI_SHELL.md +163 -53
  6. package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
  7. package/docs/RELEASES.md +16 -7
  8. package/docs/SOURCE_ARCHITECTURE.md +58 -0
  9. package/docs/TEST_TIERS.md +26 -0
  10. package/migrations/runtime/0001_cli_runtime.sql +298 -0
  11. package/package.json +45 -12
  12. package/packages/agentsam-repository/README.md +15 -0
  13. package/packages/agentsam-repository/package.json +25 -0
  14. package/packages/agentsam-repository/src/contracts.js +113 -0
  15. package/packages/agentsam-repository/src/index.js +3 -0
  16. package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
  17. package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
  18. package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
  19. package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
  20. package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
  21. package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
  22. package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
  23. package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
  24. package/packages/connectors/cloudflare/package.json +10 -0
  25. package/packages/connectors/cloudflare/src/index.js +127 -0
  26. package/packages/connectors/cloudflare/src/owner.js +76 -0
  27. package/packages/connectors/cloudflare/src/routes.js +223 -0
  28. package/packages/connectors/cloudflare/src/vault.js +80 -0
  29. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  30. package/packages/identity/package.json +2 -2
  31. package/packages/identity/src/contracts/auth-config.js +18 -7
  32. package/packages/identity/tests/auth-config.test.mjs +9 -5
  33. package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
  34. package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
  35. package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
  36. package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
  37. package/protocol/README.md +1 -0
  38. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  39. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  40. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  41. package/protocol/capabilities/manifest.json +47 -0
  42. package/protocol/context/context-budget.schema.json +10 -15
  43. package/protocol/context/context-item.schema.json +4 -5
  44. package/protocol/context/resolved-context-pack.schema.json +19 -14
  45. package/protocol/models/README.md +373 -0
  46. package/protocol/models/model-inventory-v2.schema.json +212 -0
  47. package/protocol/repository/repository-contract.schema.json +24 -0
  48. package/protocol/repository/repository-dependency.schema.json +24 -0
  49. package/protocol/repository/repository-identity.schema.json +17 -0
  50. package/protocol/rpc/v1/common.proto +16 -0
  51. package/protocol/rpc/v1/errors.proto +35 -0
  52. package/protocol/rpc/v1/knowledge.proto +77 -0
  53. package/services/knowledge/package-lock.json +333 -0
  54. package/services/knowledge/package.json +5 -1
  55. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  56. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  57. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  58. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  59. package/skills/catalog.json +18 -0
  60. package/src/agent/capability-adapter.js +25 -13
  61. package/src/agent/index.js +1 -0
  62. package/src/agent/responses-runner.js +353 -0
  63. package/src/capabilities/repository-snapshot.js +3 -3
  64. package/src/cli.js +118 -31
  65. package/src/cloudflare/cpu-profile.js +115 -0
  66. package/src/cloudflare/index.js +14 -0
  67. package/src/cloudflare/wrangler.js +132 -0
  68. package/src/commands/account-auth.js +47 -0
  69. package/src/commands/cloudflare.js +58 -0
  70. package/src/commands/connections.js +93 -0
  71. package/src/commands/context-economics.js +129 -0
  72. package/src/commands/context.js +1 -1
  73. package/src/commands/db.js +20 -3
  74. package/src/commands/deploy.js +39 -3
  75. package/src/commands/env.js +90 -0
  76. package/src/commands/eval.js +63 -0
  77. package/src/commands/interactive.js +2 -5
  78. package/src/commands/knowledge.js +12 -4
  79. package/src/commands/merkle-persist.js +30 -11
  80. package/src/commands/merkle.js +1 -1
  81. package/src/commands/models.js +149 -46
  82. package/src/commands/ollama.js +26 -0
  83. package/src/commands/preferences.js +130 -61
  84. package/src/commands/resume.js +67 -0
  85. package/src/commands/security.js +5 -3
  86. package/src/commands/shell.js +568 -119
  87. package/src/commands/tunnel.js +2 -2
  88. package/src/commands/whoami.js +86 -0
  89. package/src/context/budget.js +68 -6
  90. package/src/context/index.js +3 -1
  91. package/src/context/rehydrate.js +35 -0
  92. package/src/context/resolve.js +44 -12
  93. package/src/errors/contract.js +236 -0
  94. package/src/errors/diagnostic.js +160 -0
  95. package/src/errors/index.js +23 -0
  96. package/src/eval/context.js +191 -0
  97. package/src/eval/index.js +1 -0
  98. package/src/index.js +68 -2
  99. package/src/knowledge/service/auth.js +13 -0
  100. package/src/knowledge/service/grpc-client.js +115 -0
  101. package/src/knowledge/service/grpc-codec.js +237 -0
  102. package/src/knowledge/service/grpc-server.js +83 -0
  103. package/src/knowledge/service/job-engine.js +248 -0
  104. package/src/knowledge/service/server.js +87 -135
  105. package/src/knowledge/source.js +1 -1
  106. package/src/lib/account-session.js +98 -0
  107. package/src/lib/agent-instructions.js +73 -0
  108. package/src/lib/auth.js +4 -0
  109. package/src/lib/cli-preferences.js +55 -24
  110. package/src/lib/deploy/git-guard.js +69 -0
  111. package/src/lib/deploy/health.js +57 -0
  112. package/src/lib/deploy/local-studio.js +283 -0
  113. package/src/lib/deploy/secret-scan.js +65 -0
  114. package/src/lib/deploy-receipt/index.js +2 -2
  115. package/src/lib/detect-context.js +2 -2
  116. package/src/lib/execution-approvals.js +59 -0
  117. package/src/lib/knowledge-docker.js +6 -3
  118. package/src/lib/local-sessions.js +148 -0
  119. package/src/lib/local-status.js +1 -1
  120. package/src/lib/project-config.js +1 -1
  121. package/src/lib/provider-credentials.js +183 -0
  122. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  123. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  124. package/src/lib/slash-commands.js +23 -16
  125. package/src/local/migrations.js +93 -0
  126. package/src/local/runtime-store.js +141 -0
  127. package/src/local/sqlite.js +2 -0
  128. package/src/local-pty/server.js +113 -51
  129. package/src/models/catalog.js +135 -0
  130. package/src/models/discovery.js +292 -0
  131. package/src/models/index.js +7 -0
  132. package/src/providers/anthropic-messages.js +192 -0
  133. package/src/providers/cloudflare-chat.js +183 -0
  134. package/src/providers/factory.js +69 -0
  135. package/src/providers/gemini-generate-content.js +208 -0
  136. package/src/providers/index.js +10 -0
  137. package/src/providers/ollama-chat.js +148 -0
  138. package/src/providers/openai-responses.js +426 -0
  139. package/src/repository/index.js +14 -2
  140. package/src/rpc/generated/common_grpc_pb.js +1 -0
  141. package/src/rpc/generated/common_pb.js +536 -0
  142. package/src/rpc/generated/errors_grpc_pb.js +1 -0
  143. package/src/rpc/generated/errors_pb.js +482 -0
  144. package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
  145. package/src/rpc/generated/knowledge_pb.js +2168 -0
  146. package/src/rpc/generated/package.json +3 -0
  147. package/src/security/process.js +35 -9
  148. package/src/security/trust-boundary.js +2 -2
  149. package/src/telemetry/contracts.js +203 -0
  150. package/src/telemetry/events.js +51 -0
  151. package/src/telemetry/index.js +8 -0
  152. package/src/tools/hydrate.js +35 -0
  153. package/src/tools/index.js +1 -0
  154. package/src/ui/boot.js +15 -17
  155. package/src/ui/cli/activity.js +76 -0
  156. package/src/ui/cli/compaction.js +15 -0
  157. package/src/ui/cli/footer.js +39 -0
  158. package/src/ui/cli/help.js +192 -0
  159. package/src/ui/cli/plan.js +20 -0
  160. package/src/ui/cli/runtime-events.js +110 -0
  161. package/src/ui/cli/waiting.js +16 -0
  162. package/src/ui/merkle/render.js +1 -1
  163. package/test/account-session.test.mjs +36 -0
  164. package/test/cli/preferences-runtime.test.mjs +11 -0
  165. package/test/cli/runtime-ui.test.mjs +74 -0
  166. package/test/cli-preferences.test.mjs +26 -5
  167. package/test/cloudflare-connector.test.mjs +96 -0
  168. package/test/cloudflare-runtime.test.mjs +75 -0
  169. package/test/context.test.mjs +61 -12
  170. package/test/deploy-health-scan.test.mjs +67 -0
  171. package/test/error-diagnostics.test.mjs +115 -0
  172. package/test/eval-context.test.mjs +37 -0
  173. package/test/execution-approvals.test.mjs +27 -0
  174. package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
  175. package/test/integration/cli-help.test.mjs +37 -0
  176. package/test/integration/knowledge-rpc.test.mjs +112 -0
  177. package/test/integration/merkle-cli.test.mjs +61 -0
  178. package/test/integration/merkle-persistence-identity.test.mjs +48 -0
  179. package/test/integration/provider-env-cli.test.mjs +49 -0
  180. package/test/integration/provider-factory.test.mjs +197 -0
  181. package/test/integration/repository-company-graph.test.mjs +90 -0
  182. package/test/integration/runtime-migrations.test.mjs +82 -0
  183. package/test/knowledge-service.test.mjs +5 -0
  184. package/test/knowledge.test.mjs +16 -0
  185. package/test/live/terminal-transport.live.test.mjs +24 -0
  186. package/test/local-sessions.test.mjs +48 -0
  187. package/test/local-studio-deploy.test.mjs +83 -0
  188. package/test/model-catalog.test.mjs +43 -0
  189. package/test/models.test.mjs +127 -16
  190. package/test/npm10-lock.test.mjs +29 -0
  191. package/test/ollama.test.mjs +21 -0
  192. package/test/openai-responses.test.mjs +95 -0
  193. package/test/portable-context.test.mjs +1 -1
  194. package/test/provider-credentials.test.mjs +96 -0
  195. package/test/rehydrate.test.mjs +25 -0
  196. package/test/release-hygiene.test.mjs +13 -5
  197. package/test/responses-runner.test.mjs +150 -0
  198. package/test/shell.test.mjs +92 -23
  199. package/test/smoke.mjs +4 -1
  200. package/test/telemetry.test.mjs +79 -0
  201. package/test/terminal/local-pty.mock.test.mjs +151 -0
  202. package/test/tools-search.test.mjs +14 -1
  203. package/test/whoami-resume.test.mjs +56 -0
  204. /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
  205. /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
  206. /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
  207. /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
  208. /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
  209. /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
  210. /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
  211. /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
@@ -0,0 +1,183 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ const PROVIDER_CREDENTIALS = Object.freeze({
6
+ openai: Object.freeze({ env: 'OPENAI_API_KEY', files: ['openai.env'] }),
7
+ gemini: Object.freeze({ env: 'GEMINI_API_KEY', files: ['gemini.env'] }),
8
+ anthropic: Object.freeze({ env: 'ANTHROPIC_API_KEY', files: ['anthropic.env'] }),
9
+ grok: Object.freeze({ env: 'XAI_API_KEY', files: ['grok.env', 'xai.env'] }),
10
+ cloudflare: Object.freeze({ env: 'CLOUDFLARE_API_TOKEN', files: ['cloudflare.env'], accountEnv: ['ACCOUNT_ID', 'CLOUDFLARE_ACCOUNT_ID'] }),
11
+ });
12
+
13
+ function clean(value) { return value == null ? '' : String(value).trim(); }
14
+ function normalizeCloudflareAccountId(value) {
15
+ const id = clean(value);
16
+ if (!id) return '';
17
+ if (!/^[a-f0-9]{32}$/i.test(id)) throw new Error('invalid_cloudflare_account_id');
18
+ return id;
19
+ }
20
+
21
+ function homeDirectory(options = {}) {
22
+ return path.resolve(clean(options.home) || clean(options.env?.HOME) || clean(options.env?.USERPROFILE) || os.homedir());
23
+ }
24
+
25
+ function parseEnvValue(source, variable) {
26
+ for (const rawLine of String(source || '').split(/\r?\n/)) {
27
+ const line = rawLine.trim();
28
+ if (!line || line.startsWith('#')) continue;
29
+ const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
30
+ if (!match || match[1] !== variable) continue;
31
+ let value = match[2].trim();
32
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
33
+ return value;
34
+ }
35
+ return '';
36
+ }
37
+
38
+ function secureFile(filename) {
39
+ const stat = fs.statSync(filename);
40
+ if (!stat.isFile()) return { ok: false, error: 'not_a_file' };
41
+ if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0) return { ok: false, error: 'permissions_too_open' };
42
+ return { ok: true, mode: stat.mode & 0o777 };
43
+ }
44
+
45
+ function firstEnvValue(source, names = []) {
46
+ for (const name of names) {
47
+ const value = clean(parseEnvValue(source, name));
48
+ if (value) return value;
49
+ }
50
+ return '';
51
+ }
52
+
53
+ function firstRuntimeValue(env, names = []) {
54
+ for (const name of names) {
55
+ const value = clean(env?.[name]);
56
+ if (value) return value;
57
+ }
58
+ return '';
59
+ }
60
+
61
+ export function agentEnvDirectory(options = {}) {
62
+ return path.join(homeDirectory(options), '.agentsam', 'env.d');
63
+ }
64
+
65
+ export function agentEnvLoaderPath(options = {}) {
66
+ return path.join(homeDirectory(options), '.agentsam', 'load-agent-env.sh');
67
+ }
68
+
69
+ export function ensureAgentEnvLoader(options = {}) {
70
+ const filename = agentEnvLoaderPath(options);
71
+ fs.mkdirSync(path.dirname(filename), { recursive: true, mode: 0o700 });
72
+ const source = `# AgentSam provider environment loader. Source this file; do not execute it.
73
+ _agentsam_profile=\"\${1:-}\"
74
+ case \"\$_agentsam_profile\" in
75
+ openai|anthropic|gemini|grok|cloudflare) ;;
76
+ *) echo \"usage: source ~/.agentsam/load-agent-env.sh <openai|anthropic|gemini|grok|cloudflare>\" >&2; return 2 2>/dev/null || exit 2 ;;
77
+ esac
78
+ _agentsam_file=\"\${HOME}/.agentsam/env.d/\${_agentsam_profile}.env\"
79
+ if [ ! -f \"\$_agentsam_file\" ]; then
80
+ echo \"AgentSam provider profile not found: \$_agentsam_file\" >&2
81
+ return 1 2>/dev/null || exit 1
82
+ fi
83
+ set -a
84
+ . \"\$_agentsam_file\"
85
+ set +a
86
+ if [ \"\$_agentsam_profile\" = cloudflare ]; then
87
+ if [ -z \"\${ACCOUNT_ID:-}\" ] && [ -n \"\${CLOUDFLARE_ACCOUNT_ID:-}\" ]; then export ACCOUNT_ID=\"\$CLOUDFLARE_ACCOUNT_ID\"; fi
88
+ if [ -z \"\${CLOUDFLARE_ACCOUNT_ID:-}\" ] && [ -n \"\${ACCOUNT_ID:-}\" ]; then export CLOUDFLARE_ACCOUNT_ID=\"\$ACCOUNT_ID\"; fi
89
+ fi
90
+ unset _agentsam_file _agentsam_profile
91
+ `;
92
+ fs.writeFileSync(filename, source, { mode: 0o700 });
93
+ if (process.platform !== 'win32') fs.chmodSync(filename, 0o700);
94
+ return filename;
95
+ }
96
+
97
+ function profileTemplate(provider, options = {}) {
98
+ if (provider === 'cloudflare') {
99
+ const accountId = normalizeCloudflareAccountId(options.accountId);
100
+ return `# AgentSam Cloudflare profile\n# ACCOUNT_ID is your Cloudflare account identifier; it is not a secret.\nexport ACCOUNT_ID=\"${accountId}\"\nexport CLOUDFLARE_API_TOKEN=\"\"\n`;
101
+ }
102
+ const spec = PROVIDER_CREDENTIALS[provider];
103
+ if (!spec) throw new Error(`unsupported_provider:${provider}`);
104
+ return `# AgentSam ${provider} provider profile\nexport ${spec.env}=\"\"\n`;
105
+ }
106
+
107
+ export function ensureProviderEnvProfile(provider, options = {}) {
108
+ const spec = providerCredentialSpec(provider);
109
+ if (!spec) throw new Error(`unsupported_provider:${clean(provider).toLowerCase()}`);
110
+ const dir = agentEnvDirectory(options);
111
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
112
+ if (process.platform !== 'win32') fs.chmodSync(dir, 0o700);
113
+ const filename = path.join(dir, spec.files[0]);
114
+ let created = false;
115
+ if (!fs.existsSync(filename)) {
116
+ fs.writeFileSync(filename, profileTemplate(spec.provider, options), { mode: 0o600 });
117
+ created = true;
118
+ } else if (spec.provider === 'cloudflare' && clean(options.accountId)) {
119
+ const source = fs.readFileSync(filename, 'utf8');
120
+ const current = firstEnvValue(source, spec.accountEnv || []);
121
+ if (!current) {
122
+ const accountId = normalizeCloudflareAccountId(options.accountId);
123
+ const line = `export ACCOUNT_ID=\"${accountId}\"`;
124
+ const next = /^(?:export\s+)?ACCOUNT_ID=.*$/m.test(source)
125
+ ? source.replace(/^(?:export\s+)?ACCOUNT_ID=.*$/m, line)
126
+ : `${line}\n${source}`;
127
+ fs.writeFileSync(filename, next, { mode: 0o600 });
128
+ }
129
+ }
130
+ if (process.platform !== 'win32') fs.chmodSync(filename, 0o600);
131
+ const loader = ensureAgentEnvLoader(options);
132
+ return Object.freeze({ provider: spec.provider, file: filename, loader, created, source_command: `source ~/.agentsam/load-agent-env.sh ${spec.provider}` });
133
+ }
134
+
135
+ export function providerCredentialSpec(provider) {
136
+ const id = clean(provider).toLowerCase();
137
+ const spec = PROVIDER_CREDENTIALS[id];
138
+ return spec ? Object.freeze({ provider: id, ...spec }) : null;
139
+ }
140
+
141
+ export function resolveProviderCredential(provider, options = {}) {
142
+ const spec = providerCredentialSpec(provider);
143
+ if (!spec) return Object.freeze({ provider: clean(provider).toLowerCase(), configured: false, source: null, error: 'unsupported_provider', value: '' });
144
+ const env = options.env || process.env;
145
+ const accountIdFromEnv = firstRuntimeValue(env, spec.accountEnv || []);
146
+ const fromEnv = clean(env?.[spec.env]);
147
+ if (fromEnv) return Object.freeze({ provider: spec.provider, configured: true, source: 'environment', env: spec.env, file: null, error: null, value: fromEnv, account_id: accountIdFromEnv || null });
148
+
149
+ const dir = agentEnvDirectory({ ...options, env });
150
+ for (const basename of spec.files) {
151
+ const filename = path.join(dir, basename);
152
+ if (!fs.existsSync(filename)) continue;
153
+ try {
154
+ const safety = secureFile(filename);
155
+ if (!safety.ok) return Object.freeze({ provider: spec.provider, configured: false, source: 'agentsam_env_file', env: spec.env, file: filename, error: safety.error, value: '' });
156
+ const source = fs.readFileSync(filename, 'utf8');
157
+ const value = clean(parseEnvValue(source, spec.env));
158
+ const accountId = accountIdFromEnv || firstEnvValue(source, spec.accountEnv || []);
159
+ if (value) return Object.freeze({ provider: spec.provider, configured: true, source: 'agentsam_env_file', env: spec.env, file: filename, error: null, value, account_id: accountId || null });
160
+ return Object.freeze({ provider: spec.provider, configured: false, source: 'agentsam_env_file', env: spec.env, file: filename, error: 'credential_variable_missing', value: '' });
161
+ } catch (error) {
162
+ return Object.freeze({ provider: spec.provider, configured: false, source: 'agentsam_env_file', env: spec.env, file: filename, error: error?.message || String(error), value: '' });
163
+ }
164
+ }
165
+ return Object.freeze({ provider: spec.provider, configured: false, source: null, env: spec.env, file: null, error: null, value: '' });
166
+ }
167
+
168
+ export function describeProviderCredential(provider, options = {}) {
169
+ const resolved = resolveProviderCredential(provider, options);
170
+ return Object.freeze({
171
+ provider: resolved.provider,
172
+ configured: resolved.configured,
173
+ source: resolved.source,
174
+ env: resolved.env || null,
175
+ file: resolved.file || null,
176
+ error: resolved.error || null,
177
+ account_id: resolved.account_id || null,
178
+ });
179
+ }
180
+
181
+ export function listProviderCredentialStatus(options = {}) {
182
+ return Object.freeze(Object.keys(PROVIDER_CREDENTIALS).map((provider) => describeProviderCredential(provider, options)));
183
+ }
@@ -2,31 +2,36 @@
2
2
  * Worker API template generator.
3
3
  */
4
4
 
5
- export function workerApiTemplates({ projectName, routes, cfAccountId }) {
5
+ export function workerApiTemplates({ projectName, routes, cfAccountId, dbKind = 'd1' }) {
6
6
  const files = {};
7
7
 
8
+ const pkgScripts = {
9
+ deploy: 'wrangler deploy',
10
+ dev: 'wrangler dev',
11
+ ...(dbKind === 'hyperdrive'
12
+ ? { 'db:migrate': 'psql "$DATABASE_URL" -f migrations/001_init.sql' }
13
+ : { 'db:migrate': `wrangler d1 execute ${projectName} --file=migrations/001_init.sql --remote` }),
14
+ };
15
+
8
16
  files['package.json'] = JSON.stringify({
9
17
  name: projectName,
10
18
  version: '0.1.0',
11
19
  private: true,
12
- scripts: {
13
- deploy: 'wrangler deploy',
14
- dev: 'wrangler dev',
15
- 'db:migrate': `wrangler d1 execute ${projectName} --file=migrations/001_init.sql --remote`,
16
- },
20
+ scripts: pkgScripts,
17
21
  devDependencies: { wrangler: '^3.0.0' },
22
+ ...(dbKind === 'hyperdrive' ? { dependencies: { postgres: '^3.4.0' } } : {}),
18
23
  }, null, 2);
19
24
 
25
+ const dbBindingBlock = dbKind === 'hyperdrive'
26
+ ? `[[hyperdrive]]\nbinding = "HYPERDRIVE"\nid = "REPLACE_WITH_YOUR_HYPERDRIVE_ID"\n`
27
+ : `[[d1_databases]]\nbinding = "DB"\ndatabase_name = "${projectName}"\ndatabase_id = "REPLACE_WITH_YOUR_D1_ID"\n`;
28
+
20
29
  files['wrangler.toml'] = `name = "${projectName}"
21
30
  main = "src/index.js"
22
31
  compatibility_date = "2024-01-01"
23
32
  account_id = "${cfAccountId}"
24
-
25
- [[d1_databases]]
26
- binding = "DB"
27
- database_name = "${projectName}"
28
- database_id = "REPLACE_WITH_YOUR_D1_ID"
29
- `;
33
+ ${dbKind === 'hyperdrive' ? 'compatibility_flags = ["nodejs_compat"]\n' : ''}
34
+ ${dbBindingBlock}`;
30
35
 
31
36
  // Entry
32
37
  const routeImports = routes.map(r => `import { handle${cap(r)} } from './routes/${r}.js';`).join('\n');
@@ -83,6 +88,37 @@ ${routeMatches}
83
88
  }
84
89
 
85
90
  if (routes.includes('users')) {
91
+ if (dbKind === 'hyperdrive') {
92
+ files['src/routes/users.js'] = `import postgres from 'postgres';
93
+
94
+ export async function handleUsers(request, env) {
95
+ const sql = postgres(env.HYPERDRIVE.connectionString);
96
+ const url = new URL(request.url);
97
+ const id = url.pathname.replace('/users/', '').replace('/users', '') || null;
98
+
99
+ if (request.method === 'GET' && !id) {
100
+ const rows = await sql\`SELECT * FROM users LIMIT 50\`;
101
+ return Response.json(rows);
102
+ }
103
+ if (request.method === 'GET' && id) {
104
+ const rows = await sql\`SELECT * FROM users WHERE id = \${id}\`;
105
+ if (!rows[0]) return Response.json({ error: 'Not found' }, { status: 404 });
106
+ return Response.json(rows[0]);
107
+ }
108
+ if (request.method === 'POST') {
109
+ const body = await request.json();
110
+ const newId = crypto.randomUUID();
111
+ await sql\`INSERT INTO users (id, email) VALUES (\${newId}, \${body.email})\`;
112
+ return Response.json({ id: newId }, { status: 201 });
113
+ }
114
+ if (request.method === 'DELETE' && id) {
115
+ await sql\`DELETE FROM users WHERE id = \${id}\`;
116
+ return Response.json({ ok: true });
117
+ }
118
+ return Response.json({ error: 'Method not allowed' }, { status: 405 });
119
+ }
120
+ `;
121
+ } else {
86
122
  files['src/routes/users.js'] = `export async function handleUsers(request, env) {
87
123
  const url = new URL(request.url);
88
124
  const id = url.pathname.replace('/users/', '').replace('/users', '') || null;
@@ -109,9 +145,38 @@ ${routeMatches}
109
145
  return Response.json({ error: 'Method not allowed' }, { status: 405 });
110
146
  }
111
147
  `;
148
+ }
112
149
  }
113
150
 
114
151
  if (routes.includes('content')) {
152
+ if (dbKind === 'hyperdrive') {
153
+ files['src/routes/content.js'] = `import postgres from 'postgres';
154
+
155
+ export async function handleContent(request, env) {
156
+ const sql = postgres(env.HYPERDRIVE.connectionString);
157
+ const url = new URL(request.url);
158
+ const slug = url.pathname.replace('/content/', '').replace('/content', '') || null;
159
+
160
+ if (request.method === 'GET' && !slug) {
161
+ const rows = await sql\`SELECT id, slug, title, status FROM cms_pages LIMIT 50\`;
162
+ return Response.json(rows);
163
+ }
164
+ if (request.method === 'GET' && slug) {
165
+ const rows = await sql\`SELECT * FROM cms_pages WHERE slug = \${slug}\`;
166
+ if (!rows[0]) return Response.json({ error: 'Not found' }, { status: 404 });
167
+ return Response.json(rows[0]);
168
+ }
169
+ if (request.method === 'POST') {
170
+ const body = await request.json();
171
+ const id = crypto.randomUUID();
172
+ await sql\`INSERT INTO cms_pages (id, slug, title, template, content_json)
173
+ VALUES (\${id}, \${body.slug}, \${body.title}, \${body.template ?? 'default'}, \${JSON.stringify(body.content ?? {})})\`;
174
+ return Response.json({ id }, { status: 201 });
175
+ }
176
+ return Response.json({ error: 'Method not allowed' }, { status: 405 });
177
+ }
178
+ `;
179
+ } else {
115
180
  files['src/routes/content.js'] = `export async function handleContent(request, env) {
116
181
  const url = new URL(request.url);
117
182
  const slug = url.pathname.replace('/content/', '').replace('/content', '') || null;
@@ -136,6 +201,7 @@ ${routeMatches}
136
201
  return Response.json({ error: 'Method not allowed' }, { status: 405 });
137
202
  }
138
203
  `;
204
+ }
139
205
  }
140
206
 
141
207
  if (routes.includes('webhook')) {
@@ -154,13 +220,17 @@ ${routeMatches}
154
220
  `;
155
221
  }
156
222
 
157
- // Migration
223
+ // Migration — column syntax differs: SQLite (D1) uses INTEGER/unixepoch(), Postgres uses TIMESTAMPTZ/NOW()
224
+ const createdAtCol = dbKind === 'hyperdrive'
225
+ ? 'created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()'
226
+ : 'created_at INTEGER NOT NULL DEFAULT (unixepoch())';
227
+
158
228
  let sql = `-- ${projectName} initial schema\n\n`;
159
229
  if (routes.includes('users')) {
160
230
  sql += `CREATE TABLE IF NOT EXISTS users (
161
231
  id TEXT PRIMARY KEY,
162
232
  email TEXT NOT NULL UNIQUE,
163
- created_at INTEGER NOT NULL DEFAULT (unixepoch())
233
+ ${createdAtCol}
164
234
  );\n\n`;
165
235
  }
166
236
  if (routes.includes('content')) {
@@ -171,12 +241,23 @@ ${routeMatches}
171
241
  template TEXT NOT NULL DEFAULT 'default',
172
242
  content_json TEXT,
173
243
  status TEXT NOT NULL DEFAULT 'draft',
174
- created_at INTEGER NOT NULL DEFAULT (unixepoch())
244
+ ${createdAtCol}
175
245
  );\n\n`;
176
246
  }
177
247
 
178
248
  files['migrations/001_init.sql'] = sql;
179
249
 
250
+ const deploySteps = dbKind === 'hyperdrive'
251
+ ? `npm install
252
+ npx wrangler hyperdrive create ${projectName} --connection-string="postgres://user:pass@host:5432/db"
253
+ # paste the returned Hyperdrive id into wrangler.toml as HYPERDRIVE binding
254
+ npx wrangler deploy`
255
+ : `npm install
256
+ npx wrangler d1 create ${projectName}
257
+ # paste database_id into wrangler.toml
258
+ npx wrangler d1 execute ${projectName} --file=migrations/001_init.sql --remote
259
+ npx wrangler deploy`;
260
+
180
261
  files['README.md'] = `# ${projectName}
181
262
 
182
263
  Scaffolded by [@inneranimalmedia/agentsam-sdk](https://github.com/SamPrimeaux/agentsam-sdk).
@@ -185,14 +266,14 @@ Scaffolded by [@inneranimalmedia/agentsam-sdk](https://github.com/SamPrimeaux/ag
185
266
 
186
267
  ${routes.map(r => `- \`/${r}\``).join('\n')}
187
268
 
269
+ ## Database
270
+
271
+ ${dbKind === 'hyperdrive' ? 'Your own Postgres, via Cloudflare Hyperdrive.' : 'Cloudflare D1 (SQLite).'}
272
+
188
273
  ## Deploy
189
274
 
190
275
  \`\`\`bash
191
- npm install
192
- npx wrangler d1 create ${projectName}
193
- # paste database_id into wrangler.toml
194
- npx wrangler d1 execute ${projectName} --file=migrations/001_init.sql --remote
195
- npx wrangler deploy
276
+ ${deploySteps}
196
277
  \`\`\`
197
278
  `;
198
279
 
@@ -5,6 +5,7 @@
5
5
 
6
6
  import {
7
7
  text,
8
+ select,
8
9
  multiselect,
9
10
  confirm,
10
11
  spinner,
@@ -42,6 +43,15 @@ export async function runWorkerApiWizard() {
42
43
  });
43
44
  if (isCancel(routes)) { cancel('Cancelled.'); process.exit(0); }
44
45
 
46
+ const dbKind = await select({
47
+ message: 'Database?',
48
+ options: [
49
+ { value: 'd1', label: 'Cloudflare D1', hint: 'Native, free tier, SQLite' },
50
+ { value: 'hyperdrive', label: 'Your own Postgres (Hyperdrive)', hint: 'Supabase, Neon, RDS, etc.' },
51
+ ],
52
+ });
53
+ if (isCancel(dbKind)) { cancel('Cancelled.'); process.exit(0); }
54
+
45
55
  const cfAccountId = await text({
46
56
  message: 'Cloudflare account ID?',
47
57
  placeholder: 'abc123...',
@@ -57,20 +67,26 @@ export async function runWorkerApiWizard() {
57
67
  const s = spinner();
58
68
  s.start('Generating files...');
59
69
 
60
- const config = { projectName: projectName.trim(), routes, cfAccountId: cfAccountId.trim() };
70
+ const config = { projectName: projectName.trim(), routes, cfAccountId: cfAccountId.trim(), dbKind };
61
71
  const fileTree = workerApiTemplates(config);
62
72
  await writeFileTree(`./${config.projectName}`, fileTree);
63
73
 
64
74
  s.stop(pc.green(`Files written to ./${config.projectName}/`));
65
75
 
66
- note(
67
- [
68
- `cd ${config.projectName}`,
69
- `npm install`,
70
- `npx wrangler d1 create ${config.projectName}`,
71
- `npx wrangler d1 execute ${config.projectName} --file=migrations/001_init.sql --remote`,
72
- `npx wrangler deploy`,
73
- ].join('\n'),
74
- 'Next steps'
75
- );
76
+ const nextSteps = dbKind === 'hyperdrive'
77
+ ? [
78
+ `cd ${config.projectName}`,
79
+ `npm install`,
80
+ `npx wrangler hyperdrive create ${config.projectName} --connection-string="postgres://user:pass@host:5432/db"`,
81
+ `# paste the returned Hyperdrive id into wrangler.toml`,
82
+ `npx wrangler deploy`,
83
+ ]
84
+ : [
85
+ `cd ${config.projectName}`,
86
+ `npm install`,
87
+ `npx wrangler d1 create ${config.projectName}`,
88
+ `npx wrangler d1 execute ${config.projectName} --file=migrations/001_init.sql --remote`,
89
+ `npx wrangler deploy`,
90
+ ];
91
+ note(nextSteps.join('\n'), 'Next steps');
76
92
  }
@@ -1,35 +1,42 @@
1
- /**
2
- * Canonical slash-command surface for Agent Sam SDK CLI / shell UX.
3
- * Consumed by the interactive `agentsam shell` REPL and presentation layers.
4
- */
1
+ /** Canonical, implemented slash-command surface for Agent Sam SDK CLI. */
5
2
 
6
3
  export const SHELL_THEMES = ['NIGHT', 'DAY', 'LAVA', 'VOID'];
7
4
 
8
- /** @type {Array<{ cmd: string, description: string, lane?: string }>} */
9
5
  export const SLASH_COMMANDS = [
10
- { cmd: '/help', description: 'Show Agent Sam commands' },
6
+ { cmd: '/model', description: 'Choose exact model, reasoning level, and processing tier', lane: 'model' },
7
+ { cmd: '/reasoning', description: 'Set reasoning effort for the selected model', lane: 'model' },
8
+ { cmd: '/fast', description: 'Use provider Fast processing when the selected model supports it', lane: 'model' },
9
+ { cmd: '/flex', description: 'Use provider Flex processing when the selected model supports it', lane: 'model' },
10
+ { cmd: '/standard', description: 'Return to Standard provider processing', lane: 'model' },
11
+ { cmd: '/context', description: 'Show model context economics; add repo for Git bridge context', lane: 'context' },
11
12
  { cmd: '/status', description: 'Local project, DB, Git, and PTY health', lane: 'local' },
12
- { cmd: '/context', description: 'Current Git repository and revision', lane: 'git' },
13
+ { cmd: '/models', description: 'Probe providers and provider-verified known models', lane: 'model' },
14
+ { cmd: '/login', description: 'Sign in to Inner Animal Media and save the machine-local Agent Sam session', lane: 'identity' },
15
+ { cmd: '/logout', description: 'Sign out locally without deleting provider credentials', lane: 'identity' },
16
+ { cmd: '/whoami', description: 'Show authenticated account identity and safe credential status', lane: 'identity' },
17
+ { cmd: '/session', description: 'Show current session usage, cost, and resume receipt', lane: 'observability' },
18
+ { cmd: '/usage', description: 'Show token usage, spend breakdown, and resume command', lane: 'observability' },
19
+ { cmd: '/cf', description: 'Cloudflare native reads, Wrangler status, and CPU profile analysis', lane: 'cloudflare' },
20
+ { cmd: '/settings', description: 'Choose project, runtime, terminal, and model policy' },
13
21
  { cmd: '/pwd', description: 'Print working directory', lane: 'terminal' },
14
22
  { cmd: '/cd', description: 'Change working directory', lane: 'terminal' },
15
- { cmd: '/git', description: 'Git status, diff, branch, commit, and remote', lane: 'git' },
23
+ { cmd: '/git', description: 'Run an explicit Git subcommand', lane: 'git' },
24
+ { cmd: '/diff', description: 'Show the current Git diff', lane: 'git' },
16
25
  { cmd: '/db', description: 'Local SQLite status and query helpers', lane: 'data' },
17
- { cmd: '/agent', description: 'Send a goal to the configured Agent Sam', lane: 'agent' },
18
- { cmd: '/models', description: 'Show available model providers and local models' },
19
- { cmd: '/settings', description: 'Choose project, runtime, terminal, and model preference' },
26
+ { cmd: '/agent', description: 'Send a goal to the configured local Agent Sam runtime', lane: 'agent' },
20
27
  { cmd: '/logs', description: 'Show local Agent Sam execution events', lane: 'observability' },
21
28
  { cmd: '/deploy', description: 'Add a cloud adapter and deploy intentionally', lane: 'deploy' },
29
+ { cmd: '/clear', description: 'Clear the terminal display', lane: 'terminal' },
30
+ { cmd: '/help', description: 'Show the factual implemented command catalog' },
22
31
  { cmd: '/exit', description: 'Exit Agent Sam shell and return to the host terminal' },
23
32
  ];
24
33
 
25
- /** Shell UX rollout phases (gorilla-shell → SDK default CLI experience). */
26
34
  export const SHELL_PHASES = [
27
35
  { id: '0-prototype', label: 'Visual prototype + demo scenarios', status: 'complete' },
28
- { id: 'pty-connection', label: 'Local PTY via agentsam start-local', status: 'current' },
29
- { id: 'hud-layer', label: 'Quest log, tool gate, XP HUD', status: 'planned' },
30
- { id: 'buddy-system', label: 'In-shell Agent Sam via MCP', status: 'planned' },
36
+ { id: 'pty-connection', label: 'Local PTY via agentsam start-local', status: 'complete' },
37
+ { id: 'model-context-controls', label: 'Model, reasoning, processing, and context economics', status: 'complete' },
38
+ { id: 'run-telemetry', label: 'Provider usage, cost, resumable sessions, and permission receipts', status: 'current' },
31
39
  { id: 'dashboard-embed', label: 'Embeddable shell for IAM dashboard', status: 'planned' },
32
- { id: 'standalone-pwa', label: 'Installable PWA / SDK default shell', status: 'planned' },
33
40
  ];
34
41
 
35
42
  export function listSlashCommands(opts = {}) {
@@ -0,0 +1,93 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const DEFAULT_MIGRATIONS_DIR = path.resolve(
6
+ path.dirname(fileURLToPath(import.meta.url)),
7
+ '..',
8
+ '..',
9
+ 'migrations',
10
+ 'runtime',
11
+ );
12
+
13
+ function migrationFiles(dir) {
14
+ if (!fs.existsSync(dir)) return [];
15
+ return fs.readdirSync(dir, { withFileTypes: true })
16
+ .filter((entry) => entry.isFile() && /^\d+.*\.sql$/i.test(entry.name))
17
+ .map((entry) => entry.name)
18
+ .sort((a, b) => a.localeCompare(b));
19
+ }
20
+
21
+ function ensureLedger(db) {
22
+ db.exec(`
23
+ CREATE TABLE IF NOT EXISTS agentsam_schema_migrations (
24
+ id TEXT PRIMARY KEY,
25
+ applied_at_unix INTEGER NOT NULL DEFAULT (unixepoch()),
26
+ checksum TEXT
27
+ );
28
+ `);
29
+ }
30
+
31
+ function checksumSource(source) {
32
+ let hash = 2166136261;
33
+ for (let i = 0; i < source.length; i += 1) {
34
+ hash ^= source.charCodeAt(i);
35
+ hash = Math.imul(hash, 16777619);
36
+ }
37
+ return 'fnv1a32:' + (hash >>> 0).toString(16).padStart(8, '0');
38
+ }
39
+
40
+ export async function listAppliedMigrations(db) {
41
+ ensureLedger(db);
42
+ const result = await db.prepare(
43
+ 'SELECT id, applied_at_unix, checksum FROM agentsam_schema_migrations ORDER BY id'
44
+ ).all();
45
+ return result.results || [];
46
+ }
47
+
48
+ export async function applyRuntimeMigrations(db, options = {}) {
49
+ const dir = path.resolve(options.migrationsDir || DEFAULT_MIGRATIONS_DIR);
50
+ ensureLedger(db);
51
+ const appliedRows = await listAppliedMigrations(db);
52
+ const applied = new Map(appliedRows.map((row) => [String(row.id), row]));
53
+ const results = [];
54
+
55
+ for (const filename of migrationFiles(dir)) {
56
+ const id = filename.replace(/\.sql$/i, '');
57
+ const source = fs.readFileSync(path.join(dir, filename), 'utf8');
58
+ const checksum = checksumSource(source);
59
+ const previous = applied.get(id);
60
+
61
+ if (previous) {
62
+ if (previous.checksum && previous.checksum !== checksum) {
63
+ throw new Error('migration_checksum_mismatch:' + id);
64
+ }
65
+ results.push({ id, filename, status: 'already_applied', checksum });
66
+ continue;
67
+ }
68
+
69
+ db.exec('BEGIN IMMEDIATE');
70
+ try {
71
+ db.exec(source);
72
+ await db.prepare(
73
+ 'INSERT INTO agentsam_schema_migrations (id, checksum) VALUES (?, ?)'
74
+ ).bind(id, checksum).run();
75
+ db.exec('COMMIT');
76
+ results.push({ id, filename, status: 'applied', checksum });
77
+ } catch (error) {
78
+ try { db.exec('ROLLBACK'); } catch {}
79
+ throw new Error('migration_failed:' + id + ':' + (error?.message || error));
80
+ }
81
+ }
82
+
83
+ return Object.freeze({
84
+ migrationsDir: dir,
85
+ total: results.length,
86
+ applied: results.filter((row) => row.status === 'applied').length,
87
+ results: Object.freeze(results),
88
+ });
89
+ }
90
+
91
+ export function runtimeMigrationsDirectory() {
92
+ return DEFAULT_MIGRATIONS_DIR;
93
+ }