@noodleseed/agent-kit 0.37.0 → 0.39.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/manifest.json +75 -15
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/claude-code/examples/google-bigquery/README.md +101 -0
- package/skills/claude-code/examples/google-bigquery/noodle.json +4 -0
- package/skills/claude-code/examples/google-bigquery/package.json +16 -0
- package/skills/claude-code/examples/google-bigquery/src/server.ts +112 -0
- package/skills/claude-code/examples/google-bigquery/test/server.test.ts +44 -0
- package/skills/claude-code/examples/google-bigquery/vitest.config.ts +20 -0
- package/skills/claude-code/examples/hello/README.md +3 -0
- package/skills/claude-code/references/authoring-workflow.md +1 -1
- package/skills/claude-code/references/cli-commands.md +1 -1
- package/skills/claude-code/references/examples.md +1 -0
- package/skills/claude-code/references/feedback.md +1 -1
- package/skills/claude-code/references/sdk-surface.md +3 -2
- package/skills/codex/SKILL.md +1 -1
- package/skills/codex/examples/google-bigquery/README.md +101 -0
- package/skills/codex/examples/google-bigquery/noodle.json +4 -0
- package/skills/codex/examples/google-bigquery/package.json +16 -0
- package/skills/codex/examples/google-bigquery/src/server.ts +112 -0
- package/skills/codex/examples/google-bigquery/test/server.test.ts +44 -0
- package/skills/codex/examples/google-bigquery/vitest.config.ts +20 -0
- package/skills/codex/examples/hello/README.md +3 -0
- package/skills/codex/references/authoring-workflow.md +1 -1
- package/skills/codex/references/cli-commands.md +1 -1
- package/skills/codex/references/examples.md +1 -0
- package/skills/codex/references/feedback.md +1 -1
- package/skills/codex/references/sdk-surface.md +3 -2
package/manifest.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
"packageVersion": "0.
|
|
2
|
+
"packageVersion": "0.39.0",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"path": "skills/codex/SKILL.md",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "7cae6cd35f745b34253d759eaf51e4664ec2d3f0553a74bb2d4c21d613de06f1",
|
|
7
7
|
"agentTarget": "codex"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"path": "skills/codex/references/sdk-surface.md",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "539e46421c322837ff5a0dc30c602b9ad1b73e5da1ffd28229dcce9f0dfd12fa",
|
|
12
12
|
"agentTarget": "codex"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "skills/codex/references/cli-commands.md",
|
|
16
|
-
"sha256": "
|
|
16
|
+
"sha256": "6769d9c38708e7546de5c024fade6a4940d9c8c493c762700f6f44ff98908c02",
|
|
17
17
|
"agentTarget": "codex"
|
|
18
18
|
},
|
|
19
19
|
{
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"path": "skills/codex/references/authoring-workflow.md",
|
|
36
|
-
"sha256": "
|
|
36
|
+
"sha256": "a7cd7f62d922e573ce8c4fa36320f9f0902673981a69264a48505c2e20c08a43",
|
|
37
37
|
"agentTarget": "codex"
|
|
38
38
|
},
|
|
39
39
|
{
|
|
@@ -98,12 +98,12 @@
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"path": "skills/codex/references/examples.md",
|
|
101
|
-
"sha256": "
|
|
101
|
+
"sha256": "c74de358c682b611831353727520ca952b056cd57406a2d3468ea173296cd6cd",
|
|
102
102
|
"agentTarget": "codex"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
105
|
"path": "skills/codex/references/feedback.md",
|
|
106
|
-
"sha256": "
|
|
106
|
+
"sha256": "6de79daaf21b353a5af45a6f1117afdaa760c42b98bfa677d89577257bfcee7e",
|
|
107
107
|
"agentTarget": "codex"
|
|
108
108
|
},
|
|
109
109
|
{
|
|
@@ -381,9 +381,39 @@
|
|
|
381
381
|
"sha256": "29acd34abc3452647c912d06b55a27f6600a4fe42b68d94b3ea822d34bfe3842",
|
|
382
382
|
"agentTarget": "codex"
|
|
383
383
|
},
|
|
384
|
+
{
|
|
385
|
+
"path": "skills/codex/examples/google-bigquery/README.md",
|
|
386
|
+
"sha256": "3db7a4706cc76279dee7ae7f894f0d8c099adff160f6738be659ee1993bd537b",
|
|
387
|
+
"agentTarget": "codex"
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
"path": "skills/codex/examples/google-bigquery/noodle.json",
|
|
391
|
+
"sha256": "66b26e70d81f3aa8330d2aef30a356f1b911db029eba5441aeca3911fa1e6b36",
|
|
392
|
+
"agentTarget": "codex"
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"path": "skills/codex/examples/google-bigquery/package.json",
|
|
396
|
+
"sha256": "a0c6bd5dea7c579285b8ab737d6c893d960451ef75d26546c5d393eade87e118",
|
|
397
|
+
"agentTarget": "codex"
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
"path": "skills/codex/examples/google-bigquery/src/server.ts",
|
|
401
|
+
"sha256": "e7497ec6f45020367e2b11874e19a5f749317bfdc2acfe5b24d6c3401745e937",
|
|
402
|
+
"agentTarget": "codex"
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
"path": "skills/codex/examples/google-bigquery/test/server.test.ts",
|
|
406
|
+
"sha256": "feb43be4a59b25f16f984095e3df7b930f6a6730147eed01d6bdd1cfdf63d5a7",
|
|
407
|
+
"agentTarget": "codex"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"path": "skills/codex/examples/google-bigquery/vitest.config.ts",
|
|
411
|
+
"sha256": "5f03aa24fa3d1e014a82ab8d4cbd3c1267f9e2848c79632b3a21780e193f2765",
|
|
412
|
+
"agentTarget": "codex"
|
|
413
|
+
},
|
|
384
414
|
{
|
|
385
415
|
"path": "skills/codex/examples/hello/README.md",
|
|
386
|
-
"sha256": "
|
|
416
|
+
"sha256": "447908ce83e98acf30f17b44197dff54eb4e504c62fae12e697b1acb755c0bb8",
|
|
387
417
|
"agentTarget": "codex"
|
|
388
418
|
},
|
|
389
419
|
{
|
|
@@ -433,17 +463,17 @@
|
|
|
433
463
|
},
|
|
434
464
|
{
|
|
435
465
|
"path": "skills/claude-code/SKILL.md",
|
|
436
|
-
"sha256": "
|
|
466
|
+
"sha256": "7cae6cd35f745b34253d759eaf51e4664ec2d3f0553a74bb2d4c21d613de06f1",
|
|
437
467
|
"agentTarget": "claude-code"
|
|
438
468
|
},
|
|
439
469
|
{
|
|
440
470
|
"path": "skills/claude-code/references/sdk-surface.md",
|
|
441
|
-
"sha256": "
|
|
471
|
+
"sha256": "539e46421c322837ff5a0dc30c602b9ad1b73e5da1ffd28229dcce9f0dfd12fa",
|
|
442
472
|
"agentTarget": "claude-code"
|
|
443
473
|
},
|
|
444
474
|
{
|
|
445
475
|
"path": "skills/claude-code/references/cli-commands.md",
|
|
446
|
-
"sha256": "
|
|
476
|
+
"sha256": "6769d9c38708e7546de5c024fade6a4940d9c8c493c762700f6f44ff98908c02",
|
|
447
477
|
"agentTarget": "claude-code"
|
|
448
478
|
},
|
|
449
479
|
{
|
|
@@ -463,7 +493,7 @@
|
|
|
463
493
|
},
|
|
464
494
|
{
|
|
465
495
|
"path": "skills/claude-code/references/authoring-workflow.md",
|
|
466
|
-
"sha256": "
|
|
496
|
+
"sha256": "a7cd7f62d922e573ce8c4fa36320f9f0902673981a69264a48505c2e20c08a43",
|
|
467
497
|
"agentTarget": "claude-code"
|
|
468
498
|
},
|
|
469
499
|
{
|
|
@@ -528,12 +558,12 @@
|
|
|
528
558
|
},
|
|
529
559
|
{
|
|
530
560
|
"path": "skills/claude-code/references/examples.md",
|
|
531
|
-
"sha256": "
|
|
561
|
+
"sha256": "c74de358c682b611831353727520ca952b056cd57406a2d3468ea173296cd6cd",
|
|
532
562
|
"agentTarget": "claude-code"
|
|
533
563
|
},
|
|
534
564
|
{
|
|
535
565
|
"path": "skills/claude-code/references/feedback.md",
|
|
536
|
-
"sha256": "
|
|
566
|
+
"sha256": "6de79daaf21b353a5af45a6f1117afdaa760c42b98bfa677d89577257bfcee7e",
|
|
537
567
|
"agentTarget": "claude-code"
|
|
538
568
|
},
|
|
539
569
|
{
|
|
@@ -811,9 +841,39 @@
|
|
|
811
841
|
"sha256": "29acd34abc3452647c912d06b55a27f6600a4fe42b68d94b3ea822d34bfe3842",
|
|
812
842
|
"agentTarget": "claude-code"
|
|
813
843
|
},
|
|
844
|
+
{
|
|
845
|
+
"path": "skills/claude-code/examples/google-bigquery/README.md",
|
|
846
|
+
"sha256": "3db7a4706cc76279dee7ae7f894f0d8c099adff160f6738be659ee1993bd537b",
|
|
847
|
+
"agentTarget": "claude-code"
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
"path": "skills/claude-code/examples/google-bigquery/noodle.json",
|
|
851
|
+
"sha256": "66b26e70d81f3aa8330d2aef30a356f1b911db029eba5441aeca3911fa1e6b36",
|
|
852
|
+
"agentTarget": "claude-code"
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
"path": "skills/claude-code/examples/google-bigquery/package.json",
|
|
856
|
+
"sha256": "a0c6bd5dea7c579285b8ab737d6c893d960451ef75d26546c5d393eade87e118",
|
|
857
|
+
"agentTarget": "claude-code"
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
"path": "skills/claude-code/examples/google-bigquery/src/server.ts",
|
|
861
|
+
"sha256": "e7497ec6f45020367e2b11874e19a5f749317bfdc2acfe5b24d6c3401745e937",
|
|
862
|
+
"agentTarget": "claude-code"
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
"path": "skills/claude-code/examples/google-bigquery/test/server.test.ts",
|
|
866
|
+
"sha256": "feb43be4a59b25f16f984095e3df7b930f6a6730147eed01d6bdd1cfdf63d5a7",
|
|
867
|
+
"agentTarget": "claude-code"
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
"path": "skills/claude-code/examples/google-bigquery/vitest.config.ts",
|
|
871
|
+
"sha256": "5f03aa24fa3d1e014a82ab8d4cbd3c1267f9e2848c79632b3a21780e193f2765",
|
|
872
|
+
"agentTarget": "claude-code"
|
|
873
|
+
},
|
|
814
874
|
{
|
|
815
875
|
"path": "skills/claude-code/examples/hello/README.md",
|
|
816
|
-
"sha256": "
|
|
876
|
+
"sha256": "447908ce83e98acf30f17b44197dff54eb4e504c62fae12e697b1acb755c0bb8",
|
|
817
877
|
"agentTarget": "claude-code"
|
|
818
878
|
},
|
|
819
879
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Self-checking, self-updating agent skills for the Noodle Seed CLI. Authored in this repo by @noodle-borg/agent-kit; this is the published, independently-versioned canonical skills artifact the CLI fetches and verifies.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -3,7 +3,7 @@ name: noodle-seed
|
|
|
3
3
|
description: Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed MCP server or app authored in TypeScript with the noodle CLI.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.39.0 hash:75dd0f6f85b04858 -->
|
|
7
7
|
|
|
8
8
|
# Noodle Seed
|
|
9
9
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Google BigQuery — keyless deployed-server authentication
|
|
2
|
+
|
|
3
|
+
This flagship owns the Google Workload Identity Federation capability slot: a deployed Noodle Seed server
|
|
4
|
+
calls BigQuery with hourly short-lived Google credentials and never uploads or stores a service-account JSON
|
|
5
|
+
key. The same `googleWorkloadIdentity(...)` connection works for other Google REST APIs when their connector
|
|
6
|
+
operations declare the exact Google OAuth scopes and `https://*.googleapis.com` audience they require.
|
|
7
|
+
|
|
8
|
+
The developer authors only TypeScript:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
const google = connection(
|
|
12
|
+
'customer_google_cloud',
|
|
13
|
+
googleWorkloadIdentity({
|
|
14
|
+
provider: variable('GOOGLE_WIF_PROVIDER'),
|
|
15
|
+
access: {
|
|
16
|
+
kind: 'serviceAccountImpersonation',
|
|
17
|
+
serviceAccount: variable('GOOGLE_SERVICE_ACCOUNT'),
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
use: {
|
|
23
|
+
bigquery: bind(bigquery, { profile: 'google_wif', connection: google }),
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`GOOGLE_WIF_PROVIDER` is the non-secret provider resource
|
|
28
|
+
`projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL/providers/PROVIDER`.
|
|
29
|
+
`GOOGLE_SERVICE_ACCOUNT` is the non-secret email of the customer service account that Google will
|
|
30
|
+
impersonate. The private key does not exist in Noodle.
|
|
31
|
+
|
|
32
|
+
## Complete operator setup
|
|
33
|
+
|
|
34
|
+
After creating or joining the target Noodle organization, prepare the environment's stable workload identity.
|
|
35
|
+
This can run before the first deploy and prints copyable Google Cloud and managed-variable commands:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
noodle auth google prepare \
|
|
39
|
+
--org acme \
|
|
40
|
+
--app google-bigquery \
|
|
41
|
+
--env prod \
|
|
42
|
+
--project-number 123456789012 \
|
|
43
|
+
--pool noodle-prod \
|
|
44
|
+
--provider google-bigquery \
|
|
45
|
+
--service-account bigquery-reader@customer-project.iam.gserviceaccount.com
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The command asks for every Google-specific value Noodle cannot infer, creates no Google resources itself,
|
|
49
|
+
and prints:
|
|
50
|
+
|
|
51
|
+
- the required API-enablement, workload-pool, and OIDC-provider commands;
|
|
52
|
+
- the exact issuer, attribute mapping, tenant condition, and federated principal URI;
|
|
53
|
+
- the least-privilege `roles/iam.workloadIdentityUser` service-account binding;
|
|
54
|
+
- the exact `noodle variables set ... --runtime cloud` commands.
|
|
55
|
+
|
|
56
|
+
In the customer project, grant the service account only the data permissions the server needs. For this
|
|
57
|
+
read-only example, use dataset-level `roles/bigquery.dataViewer` and project-level
|
|
58
|
+
`roles/bigquery.jobUser` so it can create query jobs; do not grant project Owner or Editor.
|
|
59
|
+
|
|
60
|
+
After applying the printed Google and Noodle commands, deploy the authored server and validate a real token
|
|
61
|
+
exchange without running the `query_bigquery` business tool:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
noodle deploy --access owner-only
|
|
65
|
+
noodle auth google doctor --org acme --app google-bigquery --env prod
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The broker signs a one-hour RS256 OIDC subject token with the platform signing key, exchanges it only at
|
|
69
|
+
Google STS, optionally calls IAM Credentials `generateAccessToken`, and caches the final binding-scoped token
|
|
70
|
+
until five minutes before expiry. Revocation immediately stops Noodle from issuing or reusing credentials:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
noodle auth google revoke --org acme --app google-bigquery --env prod
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
An already-issued Google token can remain usable until its short expiry. Remove the customer-side IAM
|
|
77
|
+
principal binding as defense in depth. Re-preparing after revocation creates a new subject, so the old
|
|
78
|
+
Google IAM binding cannot silently become valid again.
|
|
79
|
+
|
|
80
|
+
## Direct federation instead of impersonation
|
|
81
|
+
|
|
82
|
+
Where the target Google API supports direct federated principals, omit the service account:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
googleWorkloadIdentity({
|
|
86
|
+
provider: variable('GOOGLE_WIF_PROVIDER'),
|
|
87
|
+
access: { kind: 'direct' },
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Run `noodle auth google prepare` without `--service-account`, then grant the printed federated principal the
|
|
92
|
+
least-privilege role directly on the target resource.
|
|
93
|
+
|
|
94
|
+
## Local validation
|
|
95
|
+
|
|
96
|
+
Compilation and tests need no Google credentials:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
noodle validate
|
|
100
|
+
noodle test
|
|
101
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "google-bigquery",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "vitest run",
|
|
8
|
+
"validate": "noodle validate",
|
|
9
|
+
"dev": "noodle dev",
|
|
10
|
+
"deploy": "noodle deploy"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@noodleseed/one": "latest",
|
|
14
|
+
"vitest": "latest"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
annotations,
|
|
3
|
+
bind,
|
|
4
|
+
connection,
|
|
5
|
+
connector,
|
|
6
|
+
googleWorkloadIdentity,
|
|
7
|
+
server,
|
|
8
|
+
tool,
|
|
9
|
+
variable,
|
|
10
|
+
z,
|
|
11
|
+
} from '@noodleseed/one';
|
|
12
|
+
|
|
13
|
+
const BIGQUERY_ORIGIN = 'https://bigquery.googleapis.com';
|
|
14
|
+
const BIGQUERY_READONLY = 'https://www.googleapis.com/auth/bigquery.readonly';
|
|
15
|
+
|
|
16
|
+
const bigquery = connector('google_bigquery')
|
|
17
|
+
.version('1.0.0')
|
|
18
|
+
.http({
|
|
19
|
+
baseUrl: BIGQUERY_ORIGIN,
|
|
20
|
+
allowedOrigins: [BIGQUERY_ORIGIN],
|
|
21
|
+
credentialProfiles: { google_wif: { kind: 'bearer' } },
|
|
22
|
+
operations: {
|
|
23
|
+
query: {
|
|
24
|
+
type: 'read',
|
|
25
|
+
method: 'POST',
|
|
26
|
+
path: '/bigquery/v2/projects/${args.project_id}/queries',
|
|
27
|
+
input: z.object({
|
|
28
|
+
project_id: z.string().min(1),
|
|
29
|
+
query: z.string().min(1),
|
|
30
|
+
max_results: z.number().int().min(1).max(1000).optional(),
|
|
31
|
+
}),
|
|
32
|
+
output: z.object({
|
|
33
|
+
job_complete: z.boolean().optional(),
|
|
34
|
+
schema: z.unknown().optional(),
|
|
35
|
+
rows: z.array(z.unknown()).optional(),
|
|
36
|
+
total_rows: z.string().optional(),
|
|
37
|
+
}),
|
|
38
|
+
request: {
|
|
39
|
+
query: '${args.query}',
|
|
40
|
+
useLegacySql: false,
|
|
41
|
+
maxResults: '${args.max_results}',
|
|
42
|
+
},
|
|
43
|
+
response: {
|
|
44
|
+
job_complete: '${response.jobComplete}',
|
|
45
|
+
schema: '${response.schema}',
|
|
46
|
+
rows: '${response.rows}',
|
|
47
|
+
total_rows: '${response.totalRows}',
|
|
48
|
+
},
|
|
49
|
+
credentials: {
|
|
50
|
+
profiles: ['google_wif'],
|
|
51
|
+
scopes: [BIGQUERY_READONLY],
|
|
52
|
+
audience: BIGQUERY_ORIGIN,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const google = connection(
|
|
59
|
+
'customer_google_cloud',
|
|
60
|
+
googleWorkloadIdentity({
|
|
61
|
+
provider: variable('GOOGLE_WIF_PROVIDER'),
|
|
62
|
+
access: {
|
|
63
|
+
kind: 'serviceAccountImpersonation',
|
|
64
|
+
serviceAccount: variable('GOOGLE_SERVICE_ACCOUNT'),
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export default server(
|
|
70
|
+
'google_bigquery',
|
|
71
|
+
{
|
|
72
|
+
title: 'Google BigQuery Reader',
|
|
73
|
+
version: '1.0.0',
|
|
74
|
+
use: {
|
|
75
|
+
bigquery: bind(bigquery, { profile: 'google_wif', connection: google }),
|
|
76
|
+
},
|
|
77
|
+
instructions:
|
|
78
|
+
'Run read-only GoogleSQL queries in the configured customer BigQuery project. Never invent project, dataset, or table names.',
|
|
79
|
+
},
|
|
80
|
+
[
|
|
81
|
+
tool('query_bigquery', {
|
|
82
|
+
title: 'Query BigQuery',
|
|
83
|
+
description:
|
|
84
|
+
'Run one read-only GoogleSQL query in an explicitly named Google Cloud project and return the typed BigQuery rows.',
|
|
85
|
+
input: z.object({
|
|
86
|
+
project_id: z.string().min(1).meta({ title: 'Google Cloud project' }),
|
|
87
|
+
query: z.string().min(1).meta({ title: 'GoogleSQL query' }),
|
|
88
|
+
max_results: z.number().int().min(1).max(1000).optional(),
|
|
89
|
+
}),
|
|
90
|
+
output: z.object({
|
|
91
|
+
job_complete: z.boolean(),
|
|
92
|
+
schema: z.unknown().optional(),
|
|
93
|
+
rows: z.array(z.unknown()),
|
|
94
|
+
total_rows: z.string().optional(),
|
|
95
|
+
}),
|
|
96
|
+
annotations: annotations.readOnly(),
|
|
97
|
+
fulfil({ input, connectors }) {
|
|
98
|
+
const result = connectors.bigquery.query({
|
|
99
|
+
project_id: input.project_id,
|
|
100
|
+
query: input.query,
|
|
101
|
+
max_results: input.max_results,
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
job_complete: result.job_complete,
|
|
105
|
+
schema: result.schema.optional(),
|
|
106
|
+
rows: result.rows,
|
|
107
|
+
total_rows: result.total_rows.optional(),
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import app from '../src/server.js';
|
|
3
|
+
|
|
4
|
+
describe('Google BigQuery workload-identity flagship', () => {
|
|
5
|
+
it('binds a read-only Google operation to keyless service-account impersonation', async () => {
|
|
6
|
+
const manifest = await app.toManifest();
|
|
7
|
+
expect(manifest.connectors?.bigquery).toMatchObject({
|
|
8
|
+
id: 'google_bigquery',
|
|
9
|
+
binding: {
|
|
10
|
+
profile: 'google_wif',
|
|
11
|
+
connection: {
|
|
12
|
+
id: 'customer_google_cloud',
|
|
13
|
+
source: {
|
|
14
|
+
kind: 'googleWorkloadIdentity',
|
|
15
|
+
provider: '${env.GOOGLE_WIF_PROVIDER}',
|
|
16
|
+
access: {
|
|
17
|
+
kind: 'serviceAccountImpersonation',
|
|
18
|
+
serviceAccount: '${env.GOOGLE_SERVICE_ACCOUNT}',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
expect(app.toConnectorCatalog()).toMatchObject({
|
|
25
|
+
connectors: [
|
|
26
|
+
{
|
|
27
|
+
credentialProfiles: { google_wif: { kind: 'bearer' } },
|
|
28
|
+
operations: {
|
|
29
|
+
query: {
|
|
30
|
+
credentials: {
|
|
31
|
+
profiles: ['google_wif'],
|
|
32
|
+
scopes: ['https://www.googleapis.com/auth/bigquery.readonly'],
|
|
33
|
+
audience: 'https://bigquery.googleapis.com',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
expect(JSON.stringify({ manifest, catalog: app.toConnectorCatalog() })).not.toMatch(
|
|
41
|
+
/private[_-]?key|client[_-]?secret|access[_-]?token/i,
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
resolve: {
|
|
5
|
+
alias: {
|
|
6
|
+
'@noodleseed/one': new URL('../../packages/authoring/src/index.ts', import.meta.url).pathname,
|
|
7
|
+
'@noodle-borg/capabilities': new URL(
|
|
8
|
+
'../../packages/capabilities/src/index.ts',
|
|
9
|
+
import.meta.url,
|
|
10
|
+
).pathname,
|
|
11
|
+
'@noodle-borg/compiler': new URL('../../packages/compiler/src/index.ts', import.meta.url)
|
|
12
|
+
.pathname,
|
|
13
|
+
'@noodle-borg/connector-defs': new URL(
|
|
14
|
+
'../../packages/connector-defs/src/index.ts',
|
|
15
|
+
import.meta.url,
|
|
16
|
+
).pathname,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
test: { include: ['test/**/*.test.ts'] },
|
|
20
|
+
});
|
|
@@ -9,6 +9,9 @@ When an installed Noodle Developer plugin drives this example, its skill runs th
|
|
|
9
9
|
`noodle` commands through the plugin-managed, version-pinned launcher and isolated host profile.
|
|
10
10
|
Do not install or update a global CLI: the coding agent writes and tests this source while Noodle
|
|
11
11
|
guides and operates the validate, preview, deploy, inspect, and debug workflow.
|
|
12
|
+
If that agent discovers a Noodle Seed product gap while working, the installed skill prepares a
|
|
13
|
+
sanitized `noodle feedback` proposal, discovers current fields from `noodle commands --json`, and
|
|
14
|
+
submits it only after showing the exact command and receiving explicit user approval.
|
|
12
15
|
|
|
13
16
|
```sh
|
|
14
17
|
noodle dev examples/hello/src/server.ts --app hello
|
|
@@ -41,7 +41,7 @@ Tools record connector calls into a flow; recording is not execution. Do not bra
|
|
|
41
41
|
|
|
42
42
|
HTTP connector auth variants: `bearer` (`{ kind: "bearer", secret: secret("API_TOKEN") }`), `apiKey` (`{ kind: "apiKey", header: "X-API-Key", secret: secret("API_KEY") }`), `clientCredentials`, `delegatedOAuth`, `delegatedSessionCookie`, and `delegatedTokenExchange` (per-user calls to your own API — see "Delegated downstream auth" below). Use managed `secret(...)` / `variable(...)` refs for all values that differ by org/app/env.
|
|
43
43
|
|
|
44
|
-
When one connector needs independently selectable accounts, declare catalog `credentialProfiles` plus each operation’s accepted `credentials.profiles`, then bind each `server.use` alias with `bind(connector, { profile, connection: connection("logical_id", managedSecret(secret("NAME"), { scopes, audience })) })`. The alias is the stable account boundary; never put provider account ids, labels, or credential values in it. `gmailConnector()` is the curated Gmail catalog helper; reuse it under independent aliases and accept canonical `accounts` arrays in tools (one account for writes, or an explicitly ordered supported combination for reads). See the bundled `gmail-multi-account` flagship. Bound managed secrets are supported by hosted execution. `externalExchange()` is runnable only when the deployment operator injects an exact HTTPS provider endpoint/origin/audience and durable shared subject-pin store through service ports; Noodle sends a short-lived platform-signed deployment workload assertion and accepts only a bounded bearer response. Provider implementations must consume assertion replay ids through durable shared atomic storage across instances and restarts. There is intentionally no hosted enrollment or provider CRUD surface yet. The provider wire contract is public, but its conformance kit is workspace/source-only and is not an installable npm package. Bound `clientCredentials(...)` remains fail-closed until its provider slice lands.
|
|
44
|
+
When one connector needs independently selectable accounts, declare catalog `credentialProfiles` plus each operation’s accepted `credentials.profiles`, then bind each `server.use` alias with `bind(connector, { profile, connection: connection("logical_id", managedSecret(secret("NAME"), { scopes, audience })) })`. The alias is the stable account boundary; never put provider account ids, labels, or credential values in it. `gmailConnector()` is the curated Gmail catalog helper; reuse it under independent aliases and accept canonical `accounts` arrays in tools (one account for writes, or an explicitly ordered supported combination for reads). See the bundled `gmail-multi-account` flagship. Bound managed secrets are supported by hosted execution. For deployed-server access to Google APIs, use `googleWorkloadIdentity({ provider: variable("GOOGLE_WIF_PROVIDER"), access: { kind: "direct" } })`, or add `serviceAccountImpersonation` with a managed service-account email. This is keyless Google Workload Identity Federation: exact Google scopes/audience come from the catalog operation, while `noodle auth google prepare|status|doctor|revoke` owns operator lifecycle. See the bundled `google-bigquery` flagship. `externalExchange()` is runnable only when the deployment operator injects an exact HTTPS provider endpoint/origin/audience and durable shared subject-pin store through service ports; Noodle sends a short-lived platform-signed deployment workload assertion and accepts only a bounded bearer response. Provider implementations must consume assertion replay ids through durable shared atomic storage across instances and restarts. There is intentionally no hosted enrollment or provider CRUD surface yet. The provider wire contract is public, but its conformance kit is workspace/source-only and is not an installable npm package. Bound `clientCredentials(...)` remains fail-closed until its provider slice lands.
|
|
45
45
|
|
|
46
46
|
## HTTP connector example (full server)
|
|
47
47
|
|
|
@@ -21,7 +21,7 @@ Developer-facing `noodle` commands, grouped by area. Local authoring commands (`
|
|
|
21
21
|
| `noodle setup` | Reconcile project config and local agent files (dry-run unless `--write`). |
|
|
22
22
|
| `noodle doctor` | Check login, service, project, validation, and config. |
|
|
23
23
|
| `noodle agents` | Manage AI agent skills and project context (`setup`/`context`/`doctor`). |
|
|
24
|
-
| `noodle auth` |
|
|
24
|
+
| `noodle auth` | Use `auth google` for keyless Google workload identity; diagnose downstream credentials without a business-tool call. |
|
|
25
25
|
| `noodle docs` | Export docs in an LLM-readable format. |
|
|
26
26
|
| `noodle connect` | Print connection setup for an agent host (Claude Code, Codex, Cursor, etc.). |
|
|
27
27
|
| `noodle import` | Import an OpenAPI spec into a starter `server.ts`. |
|
|
@@ -16,6 +16,7 @@ Paths are relative to this skill directory. Assets (images/fonts) are omitted fr
|
|
|
16
16
|
| `acme-bistro` | End-to-end ordering with a payment-only handoff; ships a gold-standard `design/` set (UX doc, wireframe with compliance audit, API contract). | `examples/acme-bistro/src/server.ts` + `design/` |
|
|
17
17
|
| `customer-auth` | End-user (customer) auth via OIDC/Firebase bridge with delegated credentials. | `examples/customer-auth/src/server.ts` |
|
|
18
18
|
| `gmail-multi-account` | One curated Gmail connector reused by two account bindings, canonical account arrays, exact mutation confirmation, and an accompanying personal-automation skill. | `examples/gmail-multi-account/src/server.ts` |
|
|
19
|
+
| `google-bigquery` | Keyless Google Workload Identity Federation with optional service-account impersonation, a BigQuery REST connector, and complete developer/operator setup. | `examples/google-bigquery/src/server.ts` |
|
|
19
20
|
|
|
20
21
|
## In the repository only — `examples/<name>/` on GitHub
|
|
21
22
|
|
|
@@ -39,7 +39,7 @@ noodle feedback "resources list --json omits the truncated flag the docs promise
|
|
|
39
39
|
--type fix --severity P2 --area cli --json
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
This is an example only; build the exact command for the finding and show it before execution. The message is required (1–4000 chars). Pass `--json` and parse the envelope: success is `{ok:true,data:{reference,labels}}`; a `429` means the per-user hourly budget (5) is spent — report that it was not sent and never retry-loop. The CLI attaches only the disclosed light diagnostics automatically: CLI version, OS/platform, Node version. Nothing else is collected.
|
|
42
|
+
This is an example only; build the exact command for the finding and show it before execution. Read `noodle feedback --help` for human-readable constraints or inspect `noodle commands --json` for the same machine-readable usage, enum, and length metadata instead of guessing accepted values. The message is required (1–4000 chars). Pass `--json` and parse the envelope: success is `{ok:true,data:{reference,labels}}`; a `429` means the per-user hourly budget (5) is spent — report that it was not sent and never retry-loop. The CLI attaches only the disclosed light diagnostics automatically: CLI version, OS/platform, Node version. Nothing else is collected.
|
|
43
43
|
|
|
44
44
|
## Choose the structured fields
|
|
45
45
|
|
|
@@ -24,6 +24,9 @@ Platform helper connectors are explicit subpath imports from `@noodleseed/one/pl
|
|
|
24
24
|
### Connectors & flows
|
|
25
25
|
|
|
26
26
|
- `connector("id").version(...).http({...})` or `.compute(...)` — declarative data connectors.
|
|
27
|
+
- `connection("logical_id", source)` — stable downstream-account/workload identity used by a connector binding.
|
|
28
|
+
- `bind(connector, { profile, connection })` — bind one connector alias to an exact credential profile and logical connection.
|
|
29
|
+
- `googleWorkloadIdentity({ provider: variable(...), access })` — keyless deployed-workload access to Google APIs through WIF; configure with `noodle auth google`.
|
|
27
30
|
- `when(...)` — declarative conditions for recorded flows (no native branching on runtime values).
|
|
28
31
|
|
|
29
32
|
### Resources & prompts
|
|
@@ -50,9 +53,7 @@ Platform helper connectors are explicit subpath imports from `@noodleseed/one/pl
|
|
|
50
53
|
|
|
51
54
|
### Other
|
|
52
55
|
|
|
53
|
-
- `bind`
|
|
54
56
|
- `clientCredentials`
|
|
55
|
-
- `connection`
|
|
56
57
|
- `embeddedAssistant`
|
|
57
58
|
- `externalExchange`
|
|
58
59
|
- `gmailConnector`
|
package/skills/codex/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: noodle-seed
|
|
|
3
3
|
description: Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed MCP server or app authored in TypeScript with the noodle CLI.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.39.0 hash:75dd0f6f85b04858 -->
|
|
7
7
|
|
|
8
8
|
# Noodle Seed
|
|
9
9
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Google BigQuery — keyless deployed-server authentication
|
|
2
|
+
|
|
3
|
+
This flagship owns the Google Workload Identity Federation capability slot: a deployed Noodle Seed server
|
|
4
|
+
calls BigQuery with hourly short-lived Google credentials and never uploads or stores a service-account JSON
|
|
5
|
+
key. The same `googleWorkloadIdentity(...)` connection works for other Google REST APIs when their connector
|
|
6
|
+
operations declare the exact Google OAuth scopes and `https://*.googleapis.com` audience they require.
|
|
7
|
+
|
|
8
|
+
The developer authors only TypeScript:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
const google = connection(
|
|
12
|
+
'customer_google_cloud',
|
|
13
|
+
googleWorkloadIdentity({
|
|
14
|
+
provider: variable('GOOGLE_WIF_PROVIDER'),
|
|
15
|
+
access: {
|
|
16
|
+
kind: 'serviceAccountImpersonation',
|
|
17
|
+
serviceAccount: variable('GOOGLE_SERVICE_ACCOUNT'),
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
use: {
|
|
23
|
+
bigquery: bind(bigquery, { profile: 'google_wif', connection: google }),
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`GOOGLE_WIF_PROVIDER` is the non-secret provider resource
|
|
28
|
+
`projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL/providers/PROVIDER`.
|
|
29
|
+
`GOOGLE_SERVICE_ACCOUNT` is the non-secret email of the customer service account that Google will
|
|
30
|
+
impersonate. The private key does not exist in Noodle.
|
|
31
|
+
|
|
32
|
+
## Complete operator setup
|
|
33
|
+
|
|
34
|
+
After creating or joining the target Noodle organization, prepare the environment's stable workload identity.
|
|
35
|
+
This can run before the first deploy and prints copyable Google Cloud and managed-variable commands:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
noodle auth google prepare \
|
|
39
|
+
--org acme \
|
|
40
|
+
--app google-bigquery \
|
|
41
|
+
--env prod \
|
|
42
|
+
--project-number 123456789012 \
|
|
43
|
+
--pool noodle-prod \
|
|
44
|
+
--provider google-bigquery \
|
|
45
|
+
--service-account bigquery-reader@customer-project.iam.gserviceaccount.com
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The command asks for every Google-specific value Noodle cannot infer, creates no Google resources itself,
|
|
49
|
+
and prints:
|
|
50
|
+
|
|
51
|
+
- the required API-enablement, workload-pool, and OIDC-provider commands;
|
|
52
|
+
- the exact issuer, attribute mapping, tenant condition, and federated principal URI;
|
|
53
|
+
- the least-privilege `roles/iam.workloadIdentityUser` service-account binding;
|
|
54
|
+
- the exact `noodle variables set ... --runtime cloud` commands.
|
|
55
|
+
|
|
56
|
+
In the customer project, grant the service account only the data permissions the server needs. For this
|
|
57
|
+
read-only example, use dataset-level `roles/bigquery.dataViewer` and project-level
|
|
58
|
+
`roles/bigquery.jobUser` so it can create query jobs; do not grant project Owner or Editor.
|
|
59
|
+
|
|
60
|
+
After applying the printed Google and Noodle commands, deploy the authored server and validate a real token
|
|
61
|
+
exchange without running the `query_bigquery` business tool:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
noodle deploy --access owner-only
|
|
65
|
+
noodle auth google doctor --org acme --app google-bigquery --env prod
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The broker signs a one-hour RS256 OIDC subject token with the platform signing key, exchanges it only at
|
|
69
|
+
Google STS, optionally calls IAM Credentials `generateAccessToken`, and caches the final binding-scoped token
|
|
70
|
+
until five minutes before expiry. Revocation immediately stops Noodle from issuing or reusing credentials:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
noodle auth google revoke --org acme --app google-bigquery --env prod
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
An already-issued Google token can remain usable until its short expiry. Remove the customer-side IAM
|
|
77
|
+
principal binding as defense in depth. Re-preparing after revocation creates a new subject, so the old
|
|
78
|
+
Google IAM binding cannot silently become valid again.
|
|
79
|
+
|
|
80
|
+
## Direct federation instead of impersonation
|
|
81
|
+
|
|
82
|
+
Where the target Google API supports direct federated principals, omit the service account:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
googleWorkloadIdentity({
|
|
86
|
+
provider: variable('GOOGLE_WIF_PROVIDER'),
|
|
87
|
+
access: { kind: 'direct' },
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Run `noodle auth google prepare` without `--service-account`, then grant the printed federated principal the
|
|
92
|
+
least-privilege role directly on the target resource.
|
|
93
|
+
|
|
94
|
+
## Local validation
|
|
95
|
+
|
|
96
|
+
Compilation and tests need no Google credentials:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
noodle validate
|
|
100
|
+
noodle test
|
|
101
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "google-bigquery",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "vitest run",
|
|
8
|
+
"validate": "noodle validate",
|
|
9
|
+
"dev": "noodle dev",
|
|
10
|
+
"deploy": "noodle deploy"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@noodleseed/one": "latest",
|
|
14
|
+
"vitest": "latest"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
annotations,
|
|
3
|
+
bind,
|
|
4
|
+
connection,
|
|
5
|
+
connector,
|
|
6
|
+
googleWorkloadIdentity,
|
|
7
|
+
server,
|
|
8
|
+
tool,
|
|
9
|
+
variable,
|
|
10
|
+
z,
|
|
11
|
+
} from '@noodleseed/one';
|
|
12
|
+
|
|
13
|
+
const BIGQUERY_ORIGIN = 'https://bigquery.googleapis.com';
|
|
14
|
+
const BIGQUERY_READONLY = 'https://www.googleapis.com/auth/bigquery.readonly';
|
|
15
|
+
|
|
16
|
+
const bigquery = connector('google_bigquery')
|
|
17
|
+
.version('1.0.0')
|
|
18
|
+
.http({
|
|
19
|
+
baseUrl: BIGQUERY_ORIGIN,
|
|
20
|
+
allowedOrigins: [BIGQUERY_ORIGIN],
|
|
21
|
+
credentialProfiles: { google_wif: { kind: 'bearer' } },
|
|
22
|
+
operations: {
|
|
23
|
+
query: {
|
|
24
|
+
type: 'read',
|
|
25
|
+
method: 'POST',
|
|
26
|
+
path: '/bigquery/v2/projects/${args.project_id}/queries',
|
|
27
|
+
input: z.object({
|
|
28
|
+
project_id: z.string().min(1),
|
|
29
|
+
query: z.string().min(1),
|
|
30
|
+
max_results: z.number().int().min(1).max(1000).optional(),
|
|
31
|
+
}),
|
|
32
|
+
output: z.object({
|
|
33
|
+
job_complete: z.boolean().optional(),
|
|
34
|
+
schema: z.unknown().optional(),
|
|
35
|
+
rows: z.array(z.unknown()).optional(),
|
|
36
|
+
total_rows: z.string().optional(),
|
|
37
|
+
}),
|
|
38
|
+
request: {
|
|
39
|
+
query: '${args.query}',
|
|
40
|
+
useLegacySql: false,
|
|
41
|
+
maxResults: '${args.max_results}',
|
|
42
|
+
},
|
|
43
|
+
response: {
|
|
44
|
+
job_complete: '${response.jobComplete}',
|
|
45
|
+
schema: '${response.schema}',
|
|
46
|
+
rows: '${response.rows}',
|
|
47
|
+
total_rows: '${response.totalRows}',
|
|
48
|
+
},
|
|
49
|
+
credentials: {
|
|
50
|
+
profiles: ['google_wif'],
|
|
51
|
+
scopes: [BIGQUERY_READONLY],
|
|
52
|
+
audience: BIGQUERY_ORIGIN,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const google = connection(
|
|
59
|
+
'customer_google_cloud',
|
|
60
|
+
googleWorkloadIdentity({
|
|
61
|
+
provider: variable('GOOGLE_WIF_PROVIDER'),
|
|
62
|
+
access: {
|
|
63
|
+
kind: 'serviceAccountImpersonation',
|
|
64
|
+
serviceAccount: variable('GOOGLE_SERVICE_ACCOUNT'),
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export default server(
|
|
70
|
+
'google_bigquery',
|
|
71
|
+
{
|
|
72
|
+
title: 'Google BigQuery Reader',
|
|
73
|
+
version: '1.0.0',
|
|
74
|
+
use: {
|
|
75
|
+
bigquery: bind(bigquery, { profile: 'google_wif', connection: google }),
|
|
76
|
+
},
|
|
77
|
+
instructions:
|
|
78
|
+
'Run read-only GoogleSQL queries in the configured customer BigQuery project. Never invent project, dataset, or table names.',
|
|
79
|
+
},
|
|
80
|
+
[
|
|
81
|
+
tool('query_bigquery', {
|
|
82
|
+
title: 'Query BigQuery',
|
|
83
|
+
description:
|
|
84
|
+
'Run one read-only GoogleSQL query in an explicitly named Google Cloud project and return the typed BigQuery rows.',
|
|
85
|
+
input: z.object({
|
|
86
|
+
project_id: z.string().min(1).meta({ title: 'Google Cloud project' }),
|
|
87
|
+
query: z.string().min(1).meta({ title: 'GoogleSQL query' }),
|
|
88
|
+
max_results: z.number().int().min(1).max(1000).optional(),
|
|
89
|
+
}),
|
|
90
|
+
output: z.object({
|
|
91
|
+
job_complete: z.boolean(),
|
|
92
|
+
schema: z.unknown().optional(),
|
|
93
|
+
rows: z.array(z.unknown()),
|
|
94
|
+
total_rows: z.string().optional(),
|
|
95
|
+
}),
|
|
96
|
+
annotations: annotations.readOnly(),
|
|
97
|
+
fulfil({ input, connectors }) {
|
|
98
|
+
const result = connectors.bigquery.query({
|
|
99
|
+
project_id: input.project_id,
|
|
100
|
+
query: input.query,
|
|
101
|
+
max_results: input.max_results,
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
job_complete: result.job_complete,
|
|
105
|
+
schema: result.schema.optional(),
|
|
106
|
+
rows: result.rows,
|
|
107
|
+
total_rows: result.total_rows.optional(),
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import app from '../src/server.js';
|
|
3
|
+
|
|
4
|
+
describe('Google BigQuery workload-identity flagship', () => {
|
|
5
|
+
it('binds a read-only Google operation to keyless service-account impersonation', async () => {
|
|
6
|
+
const manifest = await app.toManifest();
|
|
7
|
+
expect(manifest.connectors?.bigquery).toMatchObject({
|
|
8
|
+
id: 'google_bigquery',
|
|
9
|
+
binding: {
|
|
10
|
+
profile: 'google_wif',
|
|
11
|
+
connection: {
|
|
12
|
+
id: 'customer_google_cloud',
|
|
13
|
+
source: {
|
|
14
|
+
kind: 'googleWorkloadIdentity',
|
|
15
|
+
provider: '${env.GOOGLE_WIF_PROVIDER}',
|
|
16
|
+
access: {
|
|
17
|
+
kind: 'serviceAccountImpersonation',
|
|
18
|
+
serviceAccount: '${env.GOOGLE_SERVICE_ACCOUNT}',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
expect(app.toConnectorCatalog()).toMatchObject({
|
|
25
|
+
connectors: [
|
|
26
|
+
{
|
|
27
|
+
credentialProfiles: { google_wif: { kind: 'bearer' } },
|
|
28
|
+
operations: {
|
|
29
|
+
query: {
|
|
30
|
+
credentials: {
|
|
31
|
+
profiles: ['google_wif'],
|
|
32
|
+
scopes: ['https://www.googleapis.com/auth/bigquery.readonly'],
|
|
33
|
+
audience: 'https://bigquery.googleapis.com',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
expect(JSON.stringify({ manifest, catalog: app.toConnectorCatalog() })).not.toMatch(
|
|
41
|
+
/private[_-]?key|client[_-]?secret|access[_-]?token/i,
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
resolve: {
|
|
5
|
+
alias: {
|
|
6
|
+
'@noodleseed/one': new URL('../../packages/authoring/src/index.ts', import.meta.url).pathname,
|
|
7
|
+
'@noodle-borg/capabilities': new URL(
|
|
8
|
+
'../../packages/capabilities/src/index.ts',
|
|
9
|
+
import.meta.url,
|
|
10
|
+
).pathname,
|
|
11
|
+
'@noodle-borg/compiler': new URL('../../packages/compiler/src/index.ts', import.meta.url)
|
|
12
|
+
.pathname,
|
|
13
|
+
'@noodle-borg/connector-defs': new URL(
|
|
14
|
+
'../../packages/connector-defs/src/index.ts',
|
|
15
|
+
import.meta.url,
|
|
16
|
+
).pathname,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
test: { include: ['test/**/*.test.ts'] },
|
|
20
|
+
});
|
|
@@ -9,6 +9,9 @@ When an installed Noodle Developer plugin drives this example, its skill runs th
|
|
|
9
9
|
`noodle` commands through the plugin-managed, version-pinned launcher and isolated host profile.
|
|
10
10
|
Do not install or update a global CLI: the coding agent writes and tests this source while Noodle
|
|
11
11
|
guides and operates the validate, preview, deploy, inspect, and debug workflow.
|
|
12
|
+
If that agent discovers a Noodle Seed product gap while working, the installed skill prepares a
|
|
13
|
+
sanitized `noodle feedback` proposal, discovers current fields from `noodle commands --json`, and
|
|
14
|
+
submits it only after showing the exact command and receiving explicit user approval.
|
|
12
15
|
|
|
13
16
|
```sh
|
|
14
17
|
noodle dev examples/hello/src/server.ts --app hello
|
|
@@ -41,7 +41,7 @@ Tools record connector calls into a flow; recording is not execution. Do not bra
|
|
|
41
41
|
|
|
42
42
|
HTTP connector auth variants: `bearer` (`{ kind: "bearer", secret: secret("API_TOKEN") }`), `apiKey` (`{ kind: "apiKey", header: "X-API-Key", secret: secret("API_KEY") }`), `clientCredentials`, `delegatedOAuth`, `delegatedSessionCookie`, and `delegatedTokenExchange` (per-user calls to your own API — see "Delegated downstream auth" below). Use managed `secret(...)` / `variable(...)` refs for all values that differ by org/app/env.
|
|
43
43
|
|
|
44
|
-
When one connector needs independently selectable accounts, declare catalog `credentialProfiles` plus each operation’s accepted `credentials.profiles`, then bind each `server.use` alias with `bind(connector, { profile, connection: connection("logical_id", managedSecret(secret("NAME"), { scopes, audience })) })`. The alias is the stable account boundary; never put provider account ids, labels, or credential values in it. `gmailConnector()` is the curated Gmail catalog helper; reuse it under independent aliases and accept canonical `accounts` arrays in tools (one account for writes, or an explicitly ordered supported combination for reads). See the bundled `gmail-multi-account` flagship. Bound managed secrets are supported by hosted execution. `externalExchange()` is runnable only when the deployment operator injects an exact HTTPS provider endpoint/origin/audience and durable shared subject-pin store through service ports; Noodle sends a short-lived platform-signed deployment workload assertion and accepts only a bounded bearer response. Provider implementations must consume assertion replay ids through durable shared atomic storage across instances and restarts. There is intentionally no hosted enrollment or provider CRUD surface yet. The provider wire contract is public, but its conformance kit is workspace/source-only and is not an installable npm package. Bound `clientCredentials(...)` remains fail-closed until its provider slice lands.
|
|
44
|
+
When one connector needs independently selectable accounts, declare catalog `credentialProfiles` plus each operation’s accepted `credentials.profiles`, then bind each `server.use` alias with `bind(connector, { profile, connection: connection("logical_id", managedSecret(secret("NAME"), { scopes, audience })) })`. The alias is the stable account boundary; never put provider account ids, labels, or credential values in it. `gmailConnector()` is the curated Gmail catalog helper; reuse it under independent aliases and accept canonical `accounts` arrays in tools (one account for writes, or an explicitly ordered supported combination for reads). See the bundled `gmail-multi-account` flagship. Bound managed secrets are supported by hosted execution. For deployed-server access to Google APIs, use `googleWorkloadIdentity({ provider: variable("GOOGLE_WIF_PROVIDER"), access: { kind: "direct" } })`, or add `serviceAccountImpersonation` with a managed service-account email. This is keyless Google Workload Identity Federation: exact Google scopes/audience come from the catalog operation, while `noodle auth google prepare|status|doctor|revoke` owns operator lifecycle. See the bundled `google-bigquery` flagship. `externalExchange()` is runnable only when the deployment operator injects an exact HTTPS provider endpoint/origin/audience and durable shared subject-pin store through service ports; Noodle sends a short-lived platform-signed deployment workload assertion and accepts only a bounded bearer response. Provider implementations must consume assertion replay ids through durable shared atomic storage across instances and restarts. There is intentionally no hosted enrollment or provider CRUD surface yet. The provider wire contract is public, but its conformance kit is workspace/source-only and is not an installable npm package. Bound `clientCredentials(...)` remains fail-closed until its provider slice lands.
|
|
45
45
|
|
|
46
46
|
## HTTP connector example (full server)
|
|
47
47
|
|
|
@@ -21,7 +21,7 @@ Developer-facing `noodle` commands, grouped by area. Local authoring commands (`
|
|
|
21
21
|
| `noodle setup` | Reconcile project config and local agent files (dry-run unless `--write`). |
|
|
22
22
|
| `noodle doctor` | Check login, service, project, validation, and config. |
|
|
23
23
|
| `noodle agents` | Manage AI agent skills and project context (`setup`/`context`/`doctor`). |
|
|
24
|
-
| `noodle auth` |
|
|
24
|
+
| `noodle auth` | Use `auth google` for keyless Google workload identity; diagnose downstream credentials without a business-tool call. |
|
|
25
25
|
| `noodle docs` | Export docs in an LLM-readable format. |
|
|
26
26
|
| `noodle connect` | Print connection setup for an agent host (Claude Code, Codex, Cursor, etc.). |
|
|
27
27
|
| `noodle import` | Import an OpenAPI spec into a starter `server.ts`. |
|
|
@@ -16,6 +16,7 @@ Paths are relative to this skill directory. Assets (images/fonts) are omitted fr
|
|
|
16
16
|
| `acme-bistro` | End-to-end ordering with a payment-only handoff; ships a gold-standard `design/` set (UX doc, wireframe with compliance audit, API contract). | `examples/acme-bistro/src/server.ts` + `design/` |
|
|
17
17
|
| `customer-auth` | End-user (customer) auth via OIDC/Firebase bridge with delegated credentials. | `examples/customer-auth/src/server.ts` |
|
|
18
18
|
| `gmail-multi-account` | One curated Gmail connector reused by two account bindings, canonical account arrays, exact mutation confirmation, and an accompanying personal-automation skill. | `examples/gmail-multi-account/src/server.ts` |
|
|
19
|
+
| `google-bigquery` | Keyless Google Workload Identity Federation with optional service-account impersonation, a BigQuery REST connector, and complete developer/operator setup. | `examples/google-bigquery/src/server.ts` |
|
|
19
20
|
|
|
20
21
|
## In the repository only — `examples/<name>/` on GitHub
|
|
21
22
|
|
|
@@ -39,7 +39,7 @@ noodle feedback "resources list --json omits the truncated flag the docs promise
|
|
|
39
39
|
--type fix --severity P2 --area cli --json
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
This is an example only; build the exact command for the finding and show it before execution. The message is required (1–4000 chars). Pass `--json` and parse the envelope: success is `{ok:true,data:{reference,labels}}`; a `429` means the per-user hourly budget (5) is spent — report that it was not sent and never retry-loop. The CLI attaches only the disclosed light diagnostics automatically: CLI version, OS/platform, Node version. Nothing else is collected.
|
|
42
|
+
This is an example only; build the exact command for the finding and show it before execution. Read `noodle feedback --help` for human-readable constraints or inspect `noodle commands --json` for the same machine-readable usage, enum, and length metadata instead of guessing accepted values. The message is required (1–4000 chars). Pass `--json` and parse the envelope: success is `{ok:true,data:{reference,labels}}`; a `429` means the per-user hourly budget (5) is spent — report that it was not sent and never retry-loop. The CLI attaches only the disclosed light diagnostics automatically: CLI version, OS/platform, Node version. Nothing else is collected.
|
|
43
43
|
|
|
44
44
|
## Choose the structured fields
|
|
45
45
|
|
|
@@ -24,6 +24,9 @@ Platform helper connectors are explicit subpath imports from `@noodleseed/one/pl
|
|
|
24
24
|
### Connectors & flows
|
|
25
25
|
|
|
26
26
|
- `connector("id").version(...).http({...})` or `.compute(...)` — declarative data connectors.
|
|
27
|
+
- `connection("logical_id", source)` — stable downstream-account/workload identity used by a connector binding.
|
|
28
|
+
- `bind(connector, { profile, connection })` — bind one connector alias to an exact credential profile and logical connection.
|
|
29
|
+
- `googleWorkloadIdentity({ provider: variable(...), access })` — keyless deployed-workload access to Google APIs through WIF; configure with `noodle auth google`.
|
|
27
30
|
- `when(...)` — declarative conditions for recorded flows (no native branching on runtime values).
|
|
28
31
|
|
|
29
32
|
### Resources & prompts
|
|
@@ -50,9 +53,7 @@ Platform helper connectors are explicit subpath imports from `@noodleseed/one/pl
|
|
|
50
53
|
|
|
51
54
|
### Other
|
|
52
55
|
|
|
53
|
-
- `bind`
|
|
54
56
|
- `clientCredentials`
|
|
55
|
-
- `connection`
|
|
56
57
|
- `embeddedAssistant`
|
|
57
58
|
- `externalExchange`
|
|
58
59
|
- `gmailConnector`
|